ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.236
Committed: Mon May 22 03:28:55 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.235: +60 -39 lines
Log Message:
use glDrawPixels to draw Textview, implement indenting and render individual lines. force full refresh after expose. add lots of tooltips

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2 root 1.25
3 root 1.2 use strict;
4 root 1.25 use utf8;
5 root 1.2
6 root 1.224 # do things only needed for single-binary version (par)
7 root 1.176 BEGIN {
8     if (%PAR::LibCache) {
9     @INC = grep ref, @INC; # weed out all paths except pars loader refs
10    
11     while (my ($filename, $zip) = each %PAR::LibCache) {
12     for ($zip->memberNames) {
13     next unless /^\/root\/(.*)/;
14     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
15     unless -e "$ENV{PAR_TEMP}/$1";
16     }
17     }
18    
19 root 1.224 # TODO: pango-rc file, anybody?
20    
21 root 1.176 unshift @INC, $ENV{PAR_TEMP};
22     }
23     }
24    
25     # need to do it again because that pile of garbage called PAR nukes it before main
26 root 1.224 unshift @INC, $ENV{PAR_TEMP}
27     if %PAR::LibCache;
28 root 1.176
29 root 1.87 use Time::HiRes 'time';
30 root 1.224 use Pod::POM;
31 root 1.87 use Event;
32 root 1.13
33 elmex 1.11 use Crossfire;
34 root 1.2 use Crossfire::Protocol;
35    
36 root 1.116 use Compress::LZF;
37    
38 root 1.67 use CFClient;
39 root 1.72 use CFClient::UI;
40 root 1.141 use CFClient::MapWidget;
41 elmex 1.10
42 root 1.177 $Event::DIED = sub {
43 root 1.208 # TODO: display dialog box or so
44 root 1.177 CFClient::error $_[1];
45     };
46 root 1.176
47 root 1.178 #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
48    
49 root 1.63 our $VERSION = '0.1';
50    
51 root 1.96 my $MAX_FPS = 60;
52 root 1.90 my $MIN_FPS = 5; # unused as of yet
53 root 1.63
54 root 1.112 our $META_SERVER = "crossfire.real-time.com:13326";
55    
56 root 1.116 our $FACEMAP;
57     our $TILECACHE;
58     our $MAPCACHE;
59 root 1.19
60 root 1.87 our $LAST_REFRESH;
61     our $NOW;
62    
63 elmex 1.10 our $CFG;
64 root 1.13 our $CONN;
65 root 1.85 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
66 root 1.2
67 root 1.206 our $WANT_REFRESH;
68     our $CAN_REFRESH;
69    
70 root 1.75 our @SDL_MODES;
71 root 1.30 our $WIDTH;
72     our $HEIGHT;
73     our $FULLSCREEN;
74 root 1.99 our $FONTSIZE;
75 root 1.30
76 root 1.168 our $FONT_PROP;
77     our $FONT_FIXED;
78    
79 root 1.95 our $MAP;
80 root 1.187 our $MAPMAP;
81 root 1.69 our $MAPWIDGET;
82 root 1.112 our $BUTTONBAR;
83     our $LOGVIEW;
84     our $CONSOLE;
85     our $METASERVER;
86 root 1.199 our $LOGIN_BUTTON;
87 root 1.57
88 root 1.173 our $FLOORBOX;
89 elmex 1.125 our $GAUGES;
90 elmex 1.154 our $STATWIDS;
91 elmex 1.125
92 root 1.86 our $SDL_ACTIVE;
93 root 1.13 our %SDL_CB;
94 root 1.18
95 root 1.134 our $SDL_MIXER;
96     our @SOUNDS; # event => file mapping
97     our %AUDIO_CHUNKS; # audio files
98    
99 root 1.30 our $ALT_ENTER_MESSAGE;
100 root 1.212 our $STATUSBOX;
101 root 1.64 our $DEBUG_STATUS;
102 root 1.30
103 elmex 1.191 our $INVWIN;
104     our $INV;
105 elmex 1.217 our $INVR;
106 elmex 1.223 our $INVR_LBL;
107 elmex 1.217 our $OPENCONT;
108 elmex 1.191
109 root 1.82 sub status {
110 root 1.228 $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 20, fg => [1, 1, 0, 1]);
111 root 1.82 }
112    
113     sub debug {
114     $DEBUG_STATUS->set_text ($_[0]);
115 root 1.206 my ($w, $h) = $DEBUG_STATUS->size_request;
116     $DEBUG_STATUS->move ($WIDTH - $w, 0);
117 root 1.82 }
118    
119 root 1.84 sub start_game {
120 root 1.85 status "logging in...";
121    
122 root 1.106 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
123 root 1.84
124 root 1.116 $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
125 root 1.95 $MAP = new CFClient::Map $mapsize, $mapsize;
126 root 1.112
127     my ($host, $port) = split /:/, $CFG->{host};
128 root 1.95
129 root 1.194 $CONN = eval {
130     new conn
131     host => $host,
132     port => $port || 13327,
133     user => $CFG->{user},
134     pass => $CFG->{password},
135     mapw => $mapsize,
136     maph => $mapsize,
137     ;
138     };
139 root 1.84
140 root 1.194 if ($CONN) {
141 root 1.225 CFClient::lowdelay fileno $CONN->{fh};
142    
143 root 1.200 $LOGIN_BUTTON->set_text ("Logout");
144 root 1.225 status "login successful";
145 root 1.200
146 root 1.225 $BUTTONBAR->{children}[1]->emit ("activate")
147     if $BUTTONBAR->{children}[1]->{state};
148 root 1.85
149 root 1.194 } else {
150     status "unable to connect";
151 root 1.199 stop_game();
152 root 1.194 }
153 root 1.84 }
154    
155     sub stop_game {
156 root 1.200 return unless $CONN;
157    
158 root 1.199 status "connection closed";
159     $LOGIN_BUTTON->set_text ("Login");
160 root 1.200 $CONN->destroy;
161     $CONN = 0; # false, does not autovivify
162    
163 root 1.225 $BUTTONBAR->{children}[1]->emit ("activate")
164     unless $BUTTONBAR->{children}[1]->{state};
165    
166 root 1.200 undef $MAPCACHE;
167     undef $MAP;
168 root 1.84 }
169    
170 root 1.111 sub client_setup {
171 root 1.99 my $dialog = new CFClient::UI::FancyFrame
172 root 1.150 title => "Client Setup",
173 root 1.81 child => (my $vbox = new CFClient::UI::VBox);
174     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
175    
176 root 1.140 $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
177 root 1.81 $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
178    
179 root 1.216 $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 1, 1]);
180 root 1.150 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
181 root 1.81
182     $mode_slider->connect (changed => sub {
183     my ($self, $value) = @_;
184    
185     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
186     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
187     });
188     $mode_slider->emit (changed => $mode_slider->{range}[0]);
189 root 1.82
190 elmex 1.158 my $row = 1;
191    
192     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
193 elmex 1.166 $table->add (1, $row++, new CFClient::UI::CheckBox
194     state => $CFG->{fullscreen},
195     tooltip => "Bring the client into fullscreen mode",
196     connect_changed => sub {
197     my ($self, $value) = @_;
198     $CFG->{fullscreen} = $value;
199     }
200     );
201 root 1.85
202 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
203 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
204     state => $CFG->{fast},
205     tooltip => "Lower the visual quality considerably to speed up rendering.",
206     connect_changed => sub {
207     my ($self, $value) = @_;
208     $CFG->{fast} = $value;
209     }
210     );
211 root 1.89
212 root 1.169 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
213     $table->add (1, $row++, new CFClient::UI::Slider
214 root 1.216 range => [$CFG->{map_scale}, 0.25, 2, 0.05, 0.05],
215 root 1.169 tooltip => "Enlarge or shrink the displayed map",
216     connect_changed => sub {
217     my ($self, $value) = @_;
218 root 1.216 $CFG->{map_scale} = $value;
219 root 1.169 }
220     );
221    
222 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
223 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
224     state => $CFG->{fow_enable},
225     tooltip => "Fog-of-War marks areas that cannot be seen by the player",
226     connect_changed => sub {
227     my ($self, $value) = @_;
228     $CFG->{fow_enable} = $value;
229     }
230     );
231 root 1.97
232 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
233 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
234     range => [$CFG->{fow_intensity}, 0, 1 + 0.001, 0.001],
235     tooltip => "The higher the intensity, the lighter the Fog-of-War color",
236     connect_changed => sub {
237     my ($self, $value) = @_;
238     $CFG->{fow_intensity} = $value;
239     }
240     );
241 root 1.90
242 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
243 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
244     state => $CFG->{fow_smooth},
245     tooltip => "Smooth the Fog-of-War a bit to make it more realistic",
246     connect_changed => sub {
247     my ($self, $value) = @_;
248     $CFG->{fow_smooth} = $value;
249     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
250     }
251     );
252 root 1.91
253 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
254 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
255 root 1.216 range => [$CFG->{gui_fontsize}, 0.5, 2, 0.1, 0.1],
256 root 1.163 tooltip => "The font size used by most GUI elements",
257 root 1.216 connect_changed => sub { $CFG->{gui_fontsize} = $_[1] },
258 root 1.163 );
259 root 1.140
260 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Server Log Fontsize");
261 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
262 root 1.216 range => [$CFG->{log_fontsize}, 0.5, 2, 0.1, 0.1],
263 root 1.163 tooltip => "The font size used by the server log window only",
264 root 1.216 connect_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
265 root 1.163 );
266 root 1.105
267 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
268 root 1.163
269     $table->add (1, $row++, new CFClient::UI::Slider
270 root 1.216 range => [$CFG->{stat_fontsize}, 0.5, 2, 0.1, 0.1],
271 root 1.163 tooltip => "The font size used by the statistics window only",
272     connect_changed => sub {
273 root 1.216 $CFG->{stat_fontsize} = $_[1];
274 root 1.163 &set_stats_window_fontsize;
275     }
276     );
277 elmex 1.157
278 root 1.163 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
279     $table->add (1, $row++, new CFClient::UI::Slider
280     range => [$CFG->{gauge_size}, 0.2, 0.8, 0.02],
281     tooltip => "Adjust the size of the stats gauges at the bottom right",
282     connect_changed => sub {
283     $CFG->{gauge_size} = $_[1];
284 root 1.216 $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
285 root 1.163 }
286     );
287 elmex 1.158
288     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
289 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
290 root 1.216 range => [$CFG->{gauge_fontsize}, 0.5, 2.0, 0.1, 0.1],
291 elmex 1.166 tooltip => "Adjusts the fontsize of the gauges at the bottom right",
292 root 1.163 connect_changed => sub {
293 root 1.216 $CFG->{gauge_fontsize} = $_[1];
294 root 1.163 &set_gauge_window_fontsize;
295     }
296     );
297 elmex 1.158
298 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
299     expand => 1, align => 0, text => "Apply",
300 root 1.168 tooltip => "Apply the video settings",
301 root 1.163 connect_activate => sub {
302     video_shutdown ();
303     video_init ();
304     }
305     );
306 root 1.111
307 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
308 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
309     state => $CFG->{audio_enable},
310     tooltip => "If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
311     connect_changed => sub {
312     $CFG->{audio_enable} = $_[1];
313     }
314     );
315 root 1.140 # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
316     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
317     # $CFG->{effects_volume} = $_[1];
318     # });
319 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
320     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
321 root 1.163 $hbox->add (new CFClient::UI::CheckBox
322     expand => 1, state => $CFG->{bgm_enable},
323     tooltip => "Enable background music playing",
324     connect_changed => sub {
325     $CFG->{bgm_enable} = $_[1];
326     }
327     );
328     $hbox->add (new CFClient::UI::Slider
329     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0.1],
330     tooltip => "The volume of the background music",
331     connect_changed => sub {
332     $CFG->{bgm_volume} = $_[1];
333     CFClient::MixMusic::volume $_[1] * 128;
334     }
335     );
336 root 1.140
337 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
338     expand => 1, align => 0, text => "Apply",
339 root 1.168 tooltip => "Apply the audio settings",
340 root 1.163 connect_activate => sub {
341     audio_shutdown ();
342     audio_init ();
343     }
344     );
345 elmex 1.137
346 elmex 1.188 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Communication cmd");
347     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
348     text => $CFG->{say_command},
349     tooltip => "This is the command that will be used if you write a line in the message window entry. "
350     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
351     ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
352     connect_changed => sub {
353     my ($self, $value) = @_;
354     $CFG->{say_command} = $value;
355     }
356     );
357    
358 root 1.111 $dialog
359     }
360    
361 elmex 1.157 sub set_stats_window_fontsize {
362 elmex 1.158 for (values %{$STATWIDS}) {
363 elmex 1.157 $_->set_fontsize ($::CFG->{stat_fontsize});
364     }
365     }
366    
367 elmex 1.158 sub set_gauge_window_fontsize {
368     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
369     $_->set_fontsize ($::CFG->{gauge_fontsize});
370     }
371 root 1.169
372     # local $GAUGES->{win}{parent};#d#
373     # use PApp::Util; open D, ">:utf8", "d"; print D PApp::Util::dumpval $GAUGES->{win}; close D;
374 elmex 1.158 }
375    
376     sub make_gauge_window {
377 root 1.215 my $gh = int $HEIGHT * $CFG->{gauge_size};
378 elmex 1.158
379     my $win = new CFClient::UI::Frame (
380 root 1.215 req_y => -1,
381     user_w => $WIDTH,
382     user_h => $gh,
383 elmex 1.158 );
384 root 1.215
385 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
386     children => [
387     (new CFClient::UI::HBox expand => 1),
388 root 1.212 (new CFClient::UI::VBox children => [
389     (new CFClient::UI::Empty expand => 1),
390     (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::VBox)),
391     ]),
392 root 1.173 (my $vbox = new CFClient::UI::VBox),
393     ],
394     );
395 elmex 1.158
396 root 1.173 $vbox->add (new CFClient::UI::HBox
397     expand => 1,
398     children => [
399     (new CFClient::UI::Empty expand => 1),
400     (my $hb = new CFClient::UI::HBox),
401     ],
402     );
403 elmex 1.161
404 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
405 root 1.236 tooltip => "<b>Health points</b>. 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.");
406 root 1.172 $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
407 root 1.236 tooltip => "<b>Spell points</b>. 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.");
408 root 1.172 $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
409 root 1.236 tooltip => "<b>Grace points</b> - 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.");
410 root 1.172 $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
411 root 1.236 tooltip => "<b>Food</b>. 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.");
412 root 1.172
413 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
414 root 1.236 tooltip => "<b>Experience points and overall level</b> - 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.");
415 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
416 root 1.236 tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
417 elmex 1.158
418     $GAUGES = {
419 elmex 1.166 exp => $exp, win => $win, range => $rng,
420 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
421     };
422 root 1.169
423     &set_gauge_window_fontsize;
424    
425 elmex 1.158 $win
426     }
427    
428 elmex 1.154 sub make_stats_window {
429 root 1.183 my $tgw = new CFClient::UI::FancyFrame x => $WIDTH * 2/5, y => 0, title => "Stats";
430 root 1.155
431 root 1.185 $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
432 root 1.236 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
433     can_hover => 1, can_events => 1,
434     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
435     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
436     can_hover => 1, can_events => 1,
437     tooltip => "The map you are currently on (if supported by the server).");
438 elmex 1.156
439     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
440     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
441    
442 root 1.174 my $black = [0, 0, 0];
443    
444 root 1.180 for (
445 root 1.236 [0, 0, st_str => "Str", 30, "<b>Physical Strength</b>, determines damage dealt with weapons, how much you can carry, and how often you can attack"],
446     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
447     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
448     [0, 3, st_int => "Int", 30, "<b>Intelligence</b>, your ability to learn and use skills and incantations (both prayers and magic) and determines how much spell points you can have"],
449     [0, 4, st_wis => "Wis", 30, "<b>Wisdom</b>, the ability to learn and use divine magic (prayers). Determines how many grace points you can have"],
450     [0, 5, st_pow => "Pow", 30, "<b>Power</b>, your magical potential. Influences the strength of spell effects, and also how much your spell and grace points increase when leveling up"],
451     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
452    
453     [2, 0, st_wc => "Wc", -120, "<b>Weapon Class</b>, effectiveness of melee/missile attacks. Lower is more potent. Current weapon, level and Str are some things which effect the value of Wc. The value of Wc may range between 25 and -72."],
454     [2, 1, st_ac => "Ac", -120, "<b>Armour Class</b>, how protected you are from being hit by any attack. Lower values are better. Ac is based on your race and is modified by the Dex and current armour worn. For characters that cannot wear armour, Ac improves as their level increases."],
455     [2, 2, st_dam => "Dam", 120, "<b>Damage</b>, how much damage your melee/missile attack inflicts. Higher values indicate a greater amount of damage will be inflicted with each attack."],
456     [2, 3, st_arm => "Arm", 120, "<b>Armour</b>, how much damage (from physical attacks) will be subtracted from successful hits made upon you. This value ranges between 0 to 99%. Current armour worn primarily determines Arm value."],
457     [2, 4, st_spd => "Spd", 10.54, "<b>Speed</b>, how fast you can move. The value of speed may range between nearly 0 (\"very slow\") to higher than 5 (\"lightning fast\"). Base speed is determined from the Dex and modified downward proportionally by the amount of weight carried which exceeds the Max Carry limit. The armour worn also sets the upper limit on speed."],
458     [2, 5, st_wspd => "WSp", 10.54, "<b>Weapon Speed</b>, how many attacks you may make per unit of time (0.120s). Higher values indicate faster attack speed. Current weapon and Dex effect the value of weapon speed."],
459 root 1.180 ) {
460     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
461    
462     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
463 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
464 root 1.180 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
465 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $black, valign => 0, align => -1, text => $label, tooltip => $tooltip);
466 root 1.180 }
467 root 1.155
468 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
469 root 1.155
470 elmex 1.156 my $row = 0;
471     my $col = 0;
472 root 1.155
473 elmex 1.166 my %resist_names = (
474 root 1.235 slow => "<b>Slow</b> (slows you down when you are hit by the spell. Monsters will have an opportunity to come near you faster and hit you more often.)",
475     holyw => "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)",
476     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
477     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
478     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
479     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
480     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
481     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
482     pois => "<b>Poison</b> (resistance to getting poisoned)",
483     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
484     deat => "<b>Death</b> (resistance against death spells)",
485     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
486     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
487     fear => "<b>Fear</b> (this attack will drive you away from monsters who cast this and hit you successfully, being resistant to this helps a lot when fighting those monsters)",
488     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
489     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
490     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
491     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
492 elmex 1.166 );
493 elmex 1.156 for (qw/slow holyw conf fire depl magic
494     drain acid pois para deat phys
495     blind fear tund elec cold ghit/)
496     {
497 root 1.164 $tbl2->add ($col, $row,
498 elmex 1.156 $STATWIDS->{"res_$_"} =
499 root 1.168 new CFClient::UI::Label
500 root 1.184 font => $FONT_FIXED,
501 root 1.180 template => "-100%",
502     align => +1,
503     valign => 0,
504     can_events => 1,
505     can_hover => 1,
506     tooltip => $resist_names{$_},
507 root 1.168 );
508     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
509 root 1.185 font => $FONT_FIXED,
510 root 1.180 can_hover => 1,
511 root 1.168 can_events => 1,
512 root 1.180 image => "ui/resist/resist_$_.png",
513     tooltip => $resist_names{$_},
514 elmex 1.156 );
515    
516     $row++;
517     if ($row % 6 == 0) {
518     $col += 2;
519     $row = 0;
520     }
521     }
522    
523 elmex 1.157 &set_stats_window_fontsize;
524 elmex 1.156 update_stats_window ({});
525 root 1.155
526 elmex 1.154 $tgw
527     }
528    
529 root 1.169 sub formsep {
530     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
531     }
532    
533 elmex 1.154 sub update_stats_window {
534     my ($stats) = @_;
535    
536 elmex 1.156 # i love text protocols!!!
537 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
538     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
539     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
540     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
541     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
542 elmex 1.156 my $fo_m = 999;
543 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
544     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
545 elmex 1.156
546     $GAUGES->{hp} ->set_value ($hp, $hp_m);
547     $GAUGES->{mana} ->set_value ($sp, $sp_m);
548     $GAUGES->{food} ->set_value ($fo, $fo_m);
549     $GAUGES->{grace} ->set_value ($gr, $gr_m);
550 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
551     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
552     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
553 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
554     $GAUGES->{range} ->set_text ("Rng: " . $rng);
555 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
556 elmex 1.165 $title =~ s/^Player: //;
557     $STATWIDS->{title} ->set_text ("Title: " . $title);
558 elmex 1.156
559 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
560     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
561     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
562     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
563     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
564     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
565     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
566     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
567     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
568     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
569     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
570     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
571     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
572 elmex 1.156
573     my %tbl = (
574     phys => 100,
575     magic => 101,
576     fire => 102,
577     elec => 103,
578     cold => 104,
579     conf => 105,
580     acid => 106,
581     drain => 107,
582     ghit => 108,
583     pois => 109,
584     slow => 110,
585     para => 111,
586     tund => 112,
587     fear => 113,
588 elmex 1.165 depl => 113,
589 elmex 1.156 deat => 115,
590     holyw => 116,
591     blind => 117
592 elmex 1.154 );
593 elmex 1.156
594     for (keys %tbl) {
595     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
596     }
597    
598 elmex 1.154 }
599    
600 root 1.112 sub metaserver_dialog {
601     my $dialog = new CFClient::UI::FancyFrame
602 root 1.199 title => "Server List",
603 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
604    
605     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
606    
607     $dialog
608     }
609    
610 root 1.179 my $METASERVER_ATIME;
611    
612 root 1.112 sub update_metaserver {
613 root 1.114 my ($HOST) = @_;
614    
615 root 1.179 return if $METASERVER_ATIME > time;
616     $METASERVER_ATIME = time + 60;
617    
618 root 1.178 my $table = $METASERVER->{table};
619     $table->clear;
620 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
621 root 1.112
622     my $buf;
623    
624     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
625    
626 root 1.178 unless ($fh) {
627     $label->set_text ("unable to contact metaserver: $!");
628     return;
629     }
630    
631 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
632     my $res = sysread $fh, $buf, 8192, length $buf;
633    
634     if (!defined $res) {
635     $_[0]->w->cancel;
636 root 1.178 $label->set_text ("error while retrieving server list: $!");
637 root 1.112 } elsif ($res == 0) {
638     $_[0]->w->cancel;
639     status "server list retrieved";
640 root 1.113
641 root 1.178 utf8::decode $buf if utf8::valid $buf;
642 root 1.113
643     $table->clear;
644    
645 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
646 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
647     for 0 .. $#col;
648    
649     my @align = qw(1 0 1 1 -1);
650    
651     my $y = 0;
652 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
653 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
654    
655     for ($desc) {
656     s/<br>/\n/gi;
657     s/<li>/\n· /gi;
658     s/<.*?>//sgi;
659     s/&/&amp;/g;
660     s/</&lt;/g;
661     s/>/&gt;/g;
662     }
663    
664     $uptime = sprintf "%dd %02d:%02d:%02d",
665     (int $m->[8] / 86400),
666     (int $m->[8] / 3600) % 24,
667     (int $m->[8] / 60) % 60,
668     $m->[8] % 60;
669    
670     $m = [$users, $host, $uptime, $version, $desc];
671    
672     $y++;
673 root 1.114
674     $table->add (0, $y, new CFClient::UI::VBox children => [
675 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
676 root 1.114 $HOST->set_text ($CFG->{host} = $host);
677     }),
678     (new CFClient::UI::Empty expand => 1),
679     ]);
680    
681 root 1.224 $table->add ($_ + 1, $y, new CFClient::UI::Label
682     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
683 root 1.113 for 0 .. $#$m;
684     }
685 root 1.112 }
686     });
687     }
688    
689 root 1.111 sub server_setup {
690     my $dialog = new CFClient::UI::FancyFrame
691 root 1.150 title => "Server Setup",
692 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
693 root 1.81
694 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
695 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
696 root 1.112
697     {
698     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
699    
700 elmex 1.166 $vbox->add (
701     my $HOST = new CFClient::UI::Entry
702     expand => 1,
703     text => $CFG->{host},
704     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
705     connect_changed => sub {
706     my ($self, $value) = @_;
707     $CFG->{host} = $value;
708     }
709     );
710 root 1.112
711     $METASERVER = metaserver_dialog;
712 elmex 1.101
713 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
714     expand => 1,
715 root 1.200 text => "Server List",
716 elmex 1.166 other => $METASERVER,
717 root 1.200 tooltip => "Show a list of available crossfire servers",
718 elmex 1.166 connect_open => sub {
719     update_metaserver $HOST;
720     }
721     );
722 root 1.112 }
723 root 1.81
724 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
725 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
726     text => $CFG->{user},
727     tooltip => "The name of your character on the server",
728     connect_changed => sub {
729     my ($self, $value) = @_;
730     $CFG->{user} = $value;
731     }
732     );
733 root 1.81
734 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
735 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
736     text => $CFG->{password},
737     hidden => 1,
738     tooltip => "The password for your character",
739     connect_changed => sub {
740     my ($self, $value) = @_;
741     $CFG->{password} = $value;
742     }
743     );
744 elmex 1.101
745 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
746 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
747 root 1.81 req_w => 100,
748 root 1.216 range => [$CFG->{mapsize}, 10, 100 + 1, 1, 1],
749 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
750     ."If you set this to a high value you will be able to see further for example.",
751 root 1.81 connect_changed => sub {
752     my ($self, $value) = @_;
753    
754     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
755     },
756     );
757    
758 root 1.226 $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
759     $table->add (1, 8, new CFClient::UI::Entry
760     text => $CFG->{output_count},
761     tooltip => "Should be set to 1 unless you know what you are doing",
762     connect_changed => sub { $CFG->{output_count} = $_[1] },
763     );
764    
765     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
766     $table->add (1, 9, new CFClient::UI::Entry
767     text => $CFG->{output_sync},
768     tooltip => "Should be set to 1 unless you know what you are doing",
769     connect_changed => sub { $CFG->{output_sync} = $_[1] },
770     );
771    
772     $table->add (1, 10, $LOGIN_BUTTON = new CFClient::UI::Button
773 root 1.199 expand => 1,
774     align => 0,
775     text => "Login",
776     connect_activate => sub {
777     $CONN ? stop_game
778     : start_game;
779     },
780     );
781 root 1.82
782 root 1.98 $dialog
783 root 1.81 }
784 root 1.58
785 root 1.111 sub message_window {
786 root 1.99 my $window = new CFClient::UI::FancyFrame
787 root 1.150 title => "Messages",
788 root 1.186 border_bg => [1, 1, 1, 1],
789 root 1.236 bg => [0, 0, 0, 0.75],
790 root 1.124 user_w => int $::WIDTH / 3,
791     user_h => int $::HEIGHT / 5,
792 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
793    
794 root 1.229 $vbox->add ($LOGVIEW);
795 root 1.105
796 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
797 root 1.236 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
798     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
799     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
800     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
801 elmex 1.118 connect_focus_in => sub {
802     my ($input, $prev_focus) = @_;
803    
804     delete $input->{refocus_map};
805    
806     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
807     $input->{refocus_map} = 1;
808     }
809     delete $input->{auto_activated};
810     },
811 root 1.116 connect_activate => sub {
812 elmex 1.100 my ($input, $text) = @_;
813     $input->set_text ('');
814    
815     if ($text =~ /^\/(.*)/) {
816 root 1.123 $::CONN->user_send ($1);
817 elmex 1.100 } else {
818 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
819 root 1.123 $::CONN->user_send ("$say_cmd $text");
820 elmex 1.100 }
821 elmex 1.118 if ($input->{refocus_map}) {
822     delete $input->{refocus_map};
823     $MAPWIDGET->focus_in
824     }
825 root 1.116 },
826     connect_escape => sub {
827 elmex 1.102 $MAPWIDGET->focus_in
828 root 1.116 },
829     );
830 elmex 1.102
831     $CONSOLE = {
832     window => $window,
833     input => $input
834     };
835 root 1.99
836     $window
837     }
838    
839 elmex 1.191 sub make_inventory_window {
840 elmex 1.217 my $invwin = new CFClient::UI::FancyFrame
841     user_w => $WIDTH * (4/5), user_h => $HEIGHT * (4/5), title => "Inventory";
842    
843 root 1.230 $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
844 elmex 1.223
845     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
846     $vb1->add (my $lbl = new CFClient::UI::Label);
847     $lbl->set_text ("Player");
848     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
849    
850     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
851     $vb2->add ($INVR_LBL = new CFClient::UI::Label);
852     $INVR_LBL->set_text ("Floor");
853     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
854 elmex 1.217
855 elmex 1.191 $invwin
856     }
857    
858 root 1.89 sub sdl_init {
859 root 1.145 CFClient::SDL_Init
860 root 1.89 and die "SDL::Init failed!\n";
861     }
862    
863 root 1.134 sub video_init {
864 root 1.89 sdl_init;
865    
866 root 1.197 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
867    
868 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
869     $FULLSCREEN = $CFG->{fullscreen};
870 root 1.89 $FAST = $CFG->{fast};
871 root 1.84
872 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
873 root 1.230 or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
874 root 1.2
875 root 1.86 $SDL_ACTIVE = 1;
876 root 1.87 $LAST_REFRESH = time - 0.01;
877 root 1.45
878 root 1.67 CFClient::gl_init;
879 root 1.30
880 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
881 root 1.39
882 root 1.202 $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
883    
884 root 1.52 #############################################################################
885    
886 root 1.213 unless ($DEBUG_STATUS) {
887 root 1.202 # create the widgets
888    
889 root 1.215 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
890 root 1.202 $DEBUG_STATUS->show;
891    
892 root 1.212 $STATUSBOX = new CFClient::UI::Statusbox;
893 root 1.215 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
894 root 1.212
895     (new CFClient::UI::Frame
896 root 1.215 bg => [0, 0, 0, 0.4],
897     req_y => -1,
898     child => $STATUSBOX,
899 root 1.212 )->show;
900 root 1.202
901     CFClient::UI::FancyFrame->new (
902     border_bg => [1, 1, 1, 192/255],
903     bg => [1, 1, 1, 0],
904 root 1.236 child => ($MAPMAP = new CFClient::MapWidget::MapMap
905     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
906     ),
907 root 1.202 )->show;
908    
909     $MAPWIDGET = new CFClient::MapWidget;
910     $MAPWIDGET->connect (activate_console => sub {
911     my ($mapwidget, $preset) = @_;
912    
913     if ($CONSOLE) {
914     $CONSOLE->{input}->{auto_activated} = 1;
915     $CONSOLE->{input}->focus_in;
916 elmex 1.103
917 root 1.202 if ($preset && $CONSOLE->{input}->get_text eq '') {
918     $CONSOLE->{input}->set_text ($preset);
919     }
920 elmex 1.103 }
921 root 1.202 });
922     $MAPWIDGET->show;
923     $MAPWIDGET->focus_in;
924 root 1.81
925 root 1.229 $LOGVIEW = new CFClient::UI::TextView
926 root 1.236 expand => 1,
927     font => $FONT_FIXED,
928     fontsize => $::CFG->{log_fontsize},
929     can_hover => 1,
930     can_events => 1,
931     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
932 root 1.229 ;
933    
934 root 1.202 $BUTTONBAR = new CFClient::UI::HBox;
935 root 1.111
936 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
937     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
938     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
939     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
940     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
941     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
942 root 1.111
943 root 1.202 make_gauge_window->show; # XXX: this has to be set before make_stats_window as make_stats_window calls update_stats_window which updated the gauges also X-D
944 root 1.192
945 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
946     tooltip => "Toggles the statistics window, where all your Stats and Resistances are beign displaye at all times.");
947     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
948     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
949    
950     $BUTTONBAR->add (new CFClient::UI::Button
951     text => "Save Config",
952     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
953     connect_activate => sub {
954     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
955     status "Configuration Saved";
956     },
957     );
958 root 1.98
959 root 1.202 $BUTTONBAR->show;
960 root 1.187
961 root 1.215 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
962    
963 root 1.206 # delay till geometry is constant
964 root 1.211 $CFClient::UI::ROOT->on_post_alloc (startup => sub {
965 root 1.206 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
966 root 1.215 my $widget = $GAUGES->{win};
967     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
968 root 1.206 });
969     force_refresh ();
970 root 1.202 }
971 root 1.2 }
972    
973 root 1.134 sub video_shutdown {
974 root 1.86 undef $SDL_ACTIVE;
975 root 1.134 }
976    
977 root 1.153 my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
978 root 1.135 my $bgmusic;#TODO#hack#d#
979    
980 root 1.198 sub audio_channel_finished {
981     my ($channel) = @_;
982    
983 root 1.226 #warn "channel $channel finished\n";#d#
984 root 1.198 }
985    
986 root 1.153 sub audio_music_finished {
987     return unless $CFG->{bgm_enable};
988    
989     # TODO: hack, do play loop and mood music
990     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
991     $bgmusic->play (0);
992    
993     push @bgmusic, shift @bgmusic;
994     }
995    
996 root 1.134 sub audio_init {
997 root 1.139 if ($CFG->{audio_enable}) {
998 root 1.195 if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
999 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
1000     CFClient::Mix_AllocateChannels 8;
1001 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1002 root 1.134
1003 root 1.153 audio_music_finished;
1004 root 1.135
1005 root 1.134 while (<$fh>) {
1006     next if /^\s*#/;
1007     next if /^\s*$/;
1008    
1009     my ($file, $volume, $event) = split /\s+/, $_, 3;
1010    
1011     push @SOUNDS, "$volume,$file";
1012    
1013     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1014 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1015 root 1.134 $chunk->volume ($volume * 128 / 100);
1016     $chunk
1017     };
1018     }
1019     } else {
1020     status "unable to open sound config: $!";
1021     }
1022     }
1023     }
1024    
1025     sub audio_shutdown {
1026 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
1027 root 1.134 undef $SDL_MIXER;
1028     @SOUNDS = ();
1029     %AUDIO_CHUNKS = ();
1030 root 1.62 }
1031    
1032 root 1.87 my %animate_object;
1033     my $animate_timer;
1034    
1035     my $fps = 9;
1036    
1037 root 1.231 my %demo;#d#
1038    
1039 root 1.30 sub force_refresh {
1040 root 1.215 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1041 root 1.87 debug sprintf "%3.2f", $fps;
1042    
1043 root 1.111 $CFClient::UI::ROOT->draw;
1044 root 1.231
1045 root 1.232 $WANT_REFRESH = 0;
1046     $CAN_REFRESH = 0;
1047     $LAST_REFRESH = $NOW;
1048    
1049 root 1.231 0 && do {
1050     # some weird model-drawing code, just a joke right now
1051     use CFClient::OpenGL;
1052    
1053     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1054     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1055     $demo{r} ||= do {
1056     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1057     $mod->{v} = pack "f*", @{$mod->{v}};
1058     $_ = [scalar @$_, pack "S!*", @$_]
1059     for values %{$mod->{g}};
1060     $mod
1061     };
1062    
1063     my $r = $demo{r} or die;
1064    
1065     glDepthMask 1;
1066     glClear GL_DEPTH_BUFFER_BIT;
1067     glEnable GL_TEXTURE_2D;
1068     glEnable GL_DEPTH_TEST;
1069 root 1.233 glEnable GL_CULL_FACE;
1070 root 1.232 glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1071 root 1.231
1072     glMatrixMode GL_PROJECTION;
1073     glLoadIdentity;
1074 root 1.233 glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1075     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1076 root 1.231 glMatrixMode GL_MODELVIEW;
1077     glLoadIdentity;
1078    
1079     glPushMatrix;
1080 root 1.233 glTranslate 0, 0, -800;
1081     glScale 1, -1, 1;
1082     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1083     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1084     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1085 root 1.231 glScale 50, 50, 50;
1086    
1087     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1088     while (my ($k, $v) = each %{$r->{g}}) {
1089     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1090     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1091     }
1092 root 1.232
1093 root 1.231 glPopMatrix;
1094    
1095     glShadeModel GL_FLAT;
1096     glDisable GL_DEPTH_TEST;
1097     glDisable GL_TEXTURE_2D;
1098     glDepthMask 0;
1099    
1100     $WANT_REFRESH++;
1101     };
1102    
1103 root 1.148 CFClient::SDL_GL_SwapBuffers;
1104 root 1.1 }
1105    
1106 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1107     $NOW = time;
1108    
1109 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1110     for CFClient::SDL_PollEvent;
1111 root 1.87
1112     if (%animate_object) {
1113     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1114 root 1.206 $WANT_REFRESH++;
1115 root 1.87 }
1116    
1117 root 1.206 if ($WANT_REFRESH) {
1118 root 1.87 force_refresh;
1119     } else {
1120 root 1.206 $CAN_REFRESH = 1;
1121 root 1.87 }
1122     });
1123 root 1.64
1124 root 1.45 sub animation_start {
1125     my ($widget) = @_;
1126 root 1.87 $animate_object{$widget} = $widget;
1127 root 1.45 }
1128    
1129     sub animation_stop {
1130     my ($widget) = @_;
1131 root 1.87 delete $animate_object{$widget};
1132 root 1.45 }
1133    
1134 root 1.2 @conn::ISA = Crossfire::Protocol::;
1135 root 1.1
1136 root 1.226 sub conn::new {
1137     my $class = shift;
1138    
1139     my $self = $class->Crossfire::Protocol::new (@_);
1140    
1141     $MAPWIDGET->clr_commands;
1142    
1143     my $parser = new Pod::POM;
1144     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/command_help.pod");
1145    
1146     for my $head2 ($pod->head2) {
1147     $head2->title =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x
1148     or next;
1149    
1150     my $cmd = $1;
1151     my @args = split /\|/, $2;
1152     @args = (".*") unless @args;
1153    
1154     my $text = CFClient::pod_to_pango $head2->content;
1155    
1156     for my $arg (@args) {
1157     $arg = $arg eq ".*" ? "" : " $arg";
1158    
1159     $MAPWIDGET->add_command ("$cmd$arg", $text);
1160     }
1161     }
1162    
1163     $self
1164     }
1165    
1166 elmex 1.125 sub conn::stats_update {
1167     my ($self, $stats) = @_;
1168    
1169 root 1.224 if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1170     my $diff = $exp - $self->{prev_exp};
1171     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1172     if exists $self->{prev_exp} && $diff;
1173     $self->{prev_exp} = $exp;
1174     }
1175    
1176 elmex 1.154 update_stats_window ($stats);
1177 elmex 1.125 }
1178    
1179 root 1.89 sub conn::user_send {
1180 root 1.88 my ($self, $command) = @_;
1181    
1182 root 1.123 $self->send_command ($command);
1183 root 1.88 status $command;
1184     }
1185    
1186 root 1.119 sub conn::map_scroll {
1187     my ($self, $dx, $dy) = @_;
1188    
1189     $MAP->scroll ($dx, $dy);
1190     }
1191    
1192 root 1.94 sub conn::feed_map1a {
1193     my ($self, $data) = @_;
1194    
1195 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
1196 root 1.1
1197 root 1.95 $MAP->map1a_update ($data);
1198 root 1.69 $MAPWIDGET->update;
1199 root 1.1 }
1200    
1201 root 1.116 sub conn::flush_map {
1202     my ($self) = @_;
1203    
1204     my $map_info = delete $self->{map_info}
1205     or return;
1206    
1207     my ($hash, $x, $y, $w, $h) = @$map_info;
1208    
1209     my $data = $MAP->get_rect ($x, $y, $w, $h);
1210     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1211 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1212 root 1.116 }
1213 root 1.1
1214 root 1.2 sub conn::map_clear {
1215 root 1.1 my ($self) = @_;
1216    
1217 root 1.116 $self->flush_map;
1218 root 1.150 delete $self->{neigh_map};
1219 root 1.116
1220 root 1.95 $MAP->clear;
1221 root 1.1 }
1222    
1223 root 1.116
1224 root 1.119 sub conn::load_map($$$) {
1225     my ($self, $hash, $x, $y) = @_;
1226 root 1.115
1227 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1228     $data = Compress::LZF::decompress $data;
1229 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1230 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1231     my $data = $TILECACHE->get ($id)
1232     or next;
1233    
1234     $self->set_texture ($id => $data);
1235     }
1236     }
1237 root 1.115 }
1238    
1239 root 1.233 # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1240     # (server resource,s latency, bandwidth), so this hack is warranted.
1241     # the right fix is to make real tiled maps with an overview file
1242     sub conn::send_mapinfo {
1243     my ($self, $data, $cb) = @_;
1244    
1245     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1246     my ($wx, $wy) = ($1, $2);
1247    
1248     if ($data =~ /^spatial ([1-4]+)$/) {
1249     my @dx = (0, 0, 1, 0, -1);
1250     my @dy = (0, -1, 0, 1, 0);
1251     my ($dx, $dy);
1252    
1253     for (split //, $1) {
1254     $dx += $dx[$_];
1255     $dy += $dy[$_];
1256     }
1257    
1258     $cb->(spatial => 15,
1259     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1260     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1261     50, 50,
1262     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1263     );
1264    
1265     return;
1266     }
1267     }
1268    
1269     $self->SUPER::send_mapinfo ($data, $cb);
1270     }
1271    
1272 root 1.152 # this method does a "flood fill" into every tile direction
1273     # it assumes that tiles are arranged in a rectangular grid,
1274     # i.e. a map is the same as the left of the right map etc.
1275     # failure to comply are harmless and result in display errors
1276     # at worst.
1277 root 1.119 sub conn::flood_fill {
1278 root 1.233 my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1279 root 1.119
1280 root 1.121 # the server does not allow map paths > 6
1281 root 1.187 return if 7 <= length $path;
1282 root 1.120
1283 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1284    
1285     for (
1286 root 1.233 [1, 3, 0, -1],
1287     [2, 4, 1, 0],
1288     [3, 1, 0, 1],
1289     [4, 2, -1, 0],
1290 root 1.150 ) {
1291 root 1.233 my ($tile, $tile2, $dx, $dy) = @$_;
1292    
1293     next if $block & (1 << $tile);
1294     my $block = $block | (1 << $tile2);
1295 root 1.150
1296     my $gx = $gx + $dx;
1297     my $gy = $gy + $dy;
1298    
1299 root 1.119 next unless $flags & (1 << ($tile - 1));
1300 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1301 root 1.119
1302 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1303     if (my $info = $neigh->[$tile]) {
1304     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1305 root 1.119
1306 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1307 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1308    
1309     } else {
1310     $self->send_mapinfo ("spatial $path$tile", sub {
1311     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1312 root 1.119
1313 root 1.150 return if $mode ne "spatial";
1314 root 1.119
1315 root 1.150 $x += $MAP->ox;
1316     $y += $MAP->oy;
1317 root 1.233
1318 root 1.150 $self->load_map ($hash, $x, $y)
1319     unless $self->{neigh_map}{$hash}[5]++;#d#
1320 root 1.119
1321 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1322 root 1.119
1323 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1324 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1325     });
1326     }
1327 root 1.119 }
1328     }
1329    
1330     sub conn::map_change {
1331     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1332    
1333     $self->flush_map;
1334    
1335     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1336    
1337 root 1.187 my $mapmapw = $MAPMAP->{w};
1338     my $mapmaph = $MAPMAP->{h};
1339 root 1.150
1340     $self->{neigh_rect} = [
1341 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1342     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1343 root 1.150 ];
1344 root 1.119
1345 root 1.150 delete $self->{neigh_grid};
1346 root 1.119
1347     $x += $ox;
1348     $y += $oy;
1349    
1350     $self->{map_info} = [$hash, $x, $y, $w, $h];
1351    
1352 root 1.233 (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1353 elmex 1.158 $STATWIDS->{map}->set_text ("Map: " . $map);
1354 elmex 1.157
1355 root 1.119 $self->load_map ($hash, $x, $y);
1356 root 1.233 $self->flood_fill (0, 0, 0, "", $hash, $flags);
1357 root 1.119 }
1358    
1359 root 1.19 sub conn::face_find {
1360 root 1.116 my ($self, $facenum, $face) = @_;
1361    
1362     my $hash = "$face->{chksum},$face->{name}";
1363    
1364     my $id = $FACEMAP->get ($hash);
1365    
1366     unless ($id) {
1367     # create new id for face
1368     # i love transactions
1369     for (1..100) {
1370     my $txn = $CFClient::DB_ENV->txn_begin;
1371     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1372     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1373     $id++;
1374     if ($FACEMAP->put (id => $id) == 0
1375     && $FACEMAP->put ($hash => $id) == 0) {
1376     $txn->txn_commit;
1377    
1378     goto gotid;
1379     }
1380     }
1381     $txn->abort;
1382     }
1383 root 1.19
1384 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1385     }
1386 root 1.114
1387 root 1.116 gotid:
1388     $face->{id} = $id;
1389     $MAP->set_face ($facenum => $id);
1390 root 1.201 $self->{faceid}[$facenum] = $id;#d#
1391 root 1.116 $TILECACHE->get ($id)
1392 root 1.19 }
1393    
1394 root 1.2 sub conn::face_update {
1395 root 1.95 my ($self, $facenum, $face) = @_;
1396 root 1.19
1397 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1398    
1399     $self->set_texture ($face->{id} => delete $face->{image});
1400     }
1401 root 1.1
1402 root 1.116 sub conn::set_texture {
1403     my ($self, $id, $data) = @_;
1404 root 1.95
1405 root 1.116 $self->{texture}[$id] ||= do {
1406     my $tex =
1407     new_from_image CFClient::Texture
1408 root 1.173 $data, minify => 1, mipmap => 1;
1409 root 1.116
1410     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1411     $MAPWIDGET->update;
1412    
1413     $tex
1414     };
1415 root 1.1 }
1416    
1417 root 1.134 sub conn::sound_play {
1418     my ($self, $x, $y, $soundnum, $type) = @_;
1419    
1420 root 1.139 $SDL_MIXER
1421     or return;
1422    
1423 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1424     or return;
1425    
1426 root 1.146 $chunk->play;
1427 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1428 root 1.134 }
1429    
1430 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1431    
1432 root 1.33 sub conn::query {
1433     my ($self, $flags, $prompt) = @_;
1434    
1435 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1436     $LAST_QUERY = $prompt;
1437    
1438     my $dialog = new CFClient::UI::FancyFrame
1439     title => "Query",
1440     child => my $vbox = new CFClient::UI::VBox;
1441    
1442     $vbox->add (new CFClient::UI::Label
1443     max_w => $::WIDTH * 0.4,
1444     text => $prompt);
1445    
1446     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1447     $vbox->add (my $hbox = new CFClient::HBox);
1448     $hbox->add (new CFClient::Button
1449     text => "No",
1450     connect_activate => sub {
1451     $self->send ("reply n");
1452     $dialog->destroy;
1453     $MAPWIDGET->focus_in;
1454     }
1455     );
1456     $hbox->add (new CFClient::Button
1457     text => "Yes",
1458     connect_activate => sub {
1459     $self->send ("reply y");
1460     $dialog->destroy;
1461     $MAPWIDGET->focus_in;
1462     },
1463     );
1464    
1465     $dialog->focus_in;
1466    
1467     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1468     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1469     $vbox->add (my $entry = new CFClient::UI::Entry
1470     connect_changed => sub {
1471     $self->send ("reply $_[1]");
1472     $dialog->destroy;
1473     $MAPWIDGET->focus_in;
1474     },
1475     );
1476    
1477     $entry->focus_in;
1478    
1479     } else {
1480     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1481    
1482     $vbox->add (my $entry = new CFClient::UI::Entry
1483     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1484     connect_activate => sub {
1485     $self->send ("reply $_[1]");
1486     $dialog->destroy;
1487     $MAPWIDGET->focus_in;
1488     },
1489     );
1490    
1491     $entry->focus_in;
1492     }
1493    
1494     $dialog->show;
1495 root 1.33 }
1496    
1497 root 1.99 sub conn::drawinfo {
1498     my ($self, $color, $text) = @_;
1499    
1500     my @color = (
1501     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1502     [1.00, 1.00, 1.00],
1503 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1504 root 1.99 [1.00, 0.00, 0.00],
1505     [1.00, 0.54, 0.00],
1506     [0.11, 0.56, 1.00],
1507     [0.93, 0.46, 0.00],
1508     [0.18, 0.54, 0.34],
1509     [0.56, 0.73, 0.56],
1510     [0.80, 0.80, 0.80],
1511     [0.55, 0.41, 0.13],
1512     [0.99, 0.77, 0.26],
1513     [0.74, 0.65, 0.41],
1514     );
1515    
1516 root 1.208 my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1517    
1518 root 1.219 $text = CFClient::UI::Label::escape $text;
1519 root 1.208 $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1520     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1521 root 1.209
1522     $LOGVIEW->add_paragraph ($color[$color],
1523     join "\n", map "$time $_", split /\n/, $text);
1524 root 1.211
1525 root 1.212 $STATUSBOX->add ($text,
1526 root 1.211 group => $text,
1527 root 1.215 fg => $color[$color],
1528 root 1.211 timeout => 60,
1529     tooltip_font => $::FONT_FIXED,
1530     );
1531 root 1.208 }
1532    
1533     sub conn::drawextinfo {
1534     my ($self, $color, $type, $subtype, $message) = @_;
1535    
1536     $self->drawinfo ($color, $message);
1537 root 1.99 }
1538    
1539 root 1.144 sub conn::spell_add {
1540 root 1.143 my ($self, $spell) = @_;
1541    
1542 root 1.171 # TODO
1543     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1544 root 1.224 $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1545     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1546 root 1.144 }
1547    
1548     sub conn::spell_delete {
1549     my ($self, $spell) = @_;
1550     }
1551    
1552     sub conn::addme_success {
1553     my ($self) = @_;
1554    
1555 root 1.226 $self->send ("command output-sync $CFG->{output_sync}");
1556     $self->send ("command output-count $CFG->{output_count}");
1557 root 1.219
1558 root 1.234 my $parser = new Pod::POM;
1559     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1560    
1561     my %skill_tooltip;
1562    
1563     for my $head2 ($pod->head2) {
1564     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1565     }
1566    
1567 root 1.144 for my $skill (values %{$self->{skill_info}}) {
1568 root 1.234 $MAPWIDGET->add_command ("ready_skill $skill",
1569     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1570     . $skill_tooltip{$skill});
1571     $MAPWIDGET->add_command ("use_skill $skill",
1572     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1573     . $skill_tooltip{$skill});
1574 root 1.219 }
1575 root 1.200 }
1576    
1577     sub conn::eof {
1578 root 1.219 $MAPWIDGET->clr_commands;
1579    
1580 root 1.200 stop_game;
1581 root 1.143 }
1582    
1583 root 1.173 sub update_floorbox {
1584     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1585 root 1.200 return unless $CONN;
1586    
1587 root 1.173 $FLOORBOX->clear;
1588     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1589    
1590 root 1.207 my $count = 4;
1591     for (@{ $CONN->{container}{0} }) {
1592     if (--$count) {
1593     $FLOORBOX->add (new CFClient::UI::InventoryItem item => $_);
1594     } else {
1595     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1596     last;
1597     }
1598     }
1599 root 1.173 });
1600 root 1.206
1601     $WANT_REFRESH++;
1602 root 1.173 }
1603    
1604 root 1.169 sub conn::container_add {
1605 root 1.203 my ($self, $tag, $items) = @_;
1606    
1607 elmex 1.222 #d# print "container_add: container $tag ($self->{player}{tag})\n";
1608    
1609 elmex 1.217 if ($tag == 0) {
1610     update_floorbox;
1611 elmex 1.222 $OPENCONT = 0;
1612 elmex 1.223 $INVR_LBL->set_text ("Floor");
1613 elmex 1.217 $INVR->set_items ($self->{container}{0});
1614     } elsif ($tag == $self->{player}{tag}) {
1615 elmex 1.223 $INVR_LBL->set_text ("Player");
1616 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1617     } else {
1618     $OPENCONT = $tag;
1619 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1620 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1621     }
1622 root 1.169
1623     # $self-<{player}{tag} => player inv
1624     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1625     }
1626    
1627     sub conn::container_clear {
1628 root 1.203 my ($self, $tag) = @_;
1629 root 1.173
1630 elmex 1.222 #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1631    
1632 elmex 1.217 if ($tag == 0) {
1633     update_floorbox;
1634 elmex 1.222 $OPENCONT = 0;
1635 elmex 1.223 $INVR_LBL->set_text ("Floor");
1636 elmex 1.217 $INVR->set_items ($self->{container}{0});
1637     } elsif ($tag == $self->{player}{tag}) {
1638 elmex 1.223 $INVR_LBL->set_text ("Player");
1639 elmex 1.217 $INV->set_items ($self->{container}{$tag})
1640     } else {
1641 elmex 1.222 $OPENCONT = $tag;
1642 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1643 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1644     }
1645 elmex 1.191
1646 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1647     }
1648    
1649 root 1.173 sub conn::item_delete {
1650     my ($self, @items) = @_;
1651    
1652     for (@items) {
1653 elmex 1.222 #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1654    
1655 elmex 1.217 if ($_->{container} == 0) {
1656     update_floorbox;
1657 elmex 1.222 $OPENCONT = 0;
1658 elmex 1.223 $INVR_LBL->set_text ("Floor");
1659 elmex 1.217 $INVR->set_items ($self->{container}{0});
1660     } elsif ($_->{container} == $self->{player}{tag}) {
1661 elmex 1.223 $INVR_LBL->set_text ("Player");
1662 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1663     } else {
1664 elmex 1.222 $OPENCONT = $_->{container};
1665 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1666 elmex 1.222 $INVR->set_items ($self->{container}{$_->{container}});
1667 elmex 1.217 }
1668 root 1.173 }
1669     }
1670    
1671     sub conn::item_update {
1672     my ($self, $item) = @_;
1673    
1674 elmex 1.222 #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($OPENCONT)\n";
1675    
1676     if ($item->{tag} == $OPENCONT && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1677     $OPENCONT = 0;
1678 elmex 1.223 $INVR_LBL->set_text ("Floor");
1679 elmex 1.217 $INVR->set_items ($self->{container}{0});
1680 elmex 1.223
1681     $item->{widget}->update_item
1682     if $item->{widget};
1683 elmex 1.222 } else {
1684     if ($item->{container} == 0) {
1685     update_floorbox;
1686     $OPENCONT = 0;
1687 elmex 1.223 $INVR_LBL->set_text ("Floor");
1688 elmex 1.222 $INVR->set_items ($self->{container}{0});
1689     } elsif ($item->{container} == $self->{player}{tag}) {
1690     $INV->set_items ($self->{container}{$item->{container}})
1691     }
1692 elmex 1.217 }
1693 root 1.173 }
1694    
1695 root 1.87 %SDL_CB = (
1696 root 1.145 CFClient::SDL_QUIT => sub {
1697 root 1.87 Event::unloop -1;
1698     },
1699 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1700 root 1.87 },
1701 root 1.206 CFClient::SDL_VIDEOEXPOSE => sub {
1702 root 1.236 CFClient::UI::full_refresh;
1703 root 1.206 },
1704 root 1.153 CFClient::SDL_ACTIVEEVENT => sub {
1705     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1706 root 1.87 },
1707 root 1.145 CFClient::SDL_KEYDOWN => sub {
1708 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1709 root 1.87 # alt-enter
1710 root 1.134 video_shutdown;
1711 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1712 root 1.134 video_init;
1713 root 1.87 } else {
1714 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1715 elmex 1.23 }
1716 root 1.87 },
1717 root 1.198 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1718     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1719 root 1.153 CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1720 root 1.198 CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1721     CFClient::SDL_USEREVENT => sub {
1722     if ($_[0]{code} == 1) {
1723     audio_channel_finished $_[0]{data1};
1724     } elsif ($_[0]{code} == 0) {
1725     audio_music_finished;
1726     }
1727     },
1728 root 1.87 );
1729 elmex 1.23
1730 root 1.1 #############################################################################
1731    
1732 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1733    
1734 root 1.205 {
1735     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1736 root 1.194
1737 root 1.205 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1738 root 1.114
1739 root 1.205 $TILECACHE = CFClient::db_table "tilecache";
1740     $FACEMAP = CFClient::db_table "facemap";
1741 root 1.90
1742 root 1.205 my %DEF_CFG = (
1743     sdl_mode => 0,
1744     width => 640,
1745     height => 480,
1746     fullscreen => 0,
1747     fast => 0,
1748 root 1.230 map_scale => 1,
1749 root 1.205 fow_enable => 1,
1750     fow_intensity => 0.45,
1751     fow_smooth => 0,
1752     gui_fontsize => 1,
1753     log_fontsize => 1,
1754 root 1.206 gauge_fontsize=> 1,
1755     gauge_size => 0.35,
1756 root 1.205 stat_fontsize => 1,
1757     mapsize => 100,
1758     host => "crossfire.schmorp.de",
1759     say_command => 'say',
1760     audio_enable => 1,
1761     bgm_enable => 1,
1762     bgm_volume => 0.25,
1763 root 1.226 output_sync => 1,
1764     output_count => 1,
1765 root 1.205 );
1766 root 1.87
1767 root 1.205 while (my ($k, $v) = each %DEF_CFG) {
1768     $CFG->{$k} = $v unless exists $CFG->{$k};
1769     }
1770    
1771     sdl_init;
1772    
1773     @SDL_MODES = reverse
1774     grep $_->[0] >= 640 && $_->[1] >= 480,
1775     CFClient::SDL_ListModes;
1776    
1777     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1778    
1779     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1780    
1781     {
1782     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1783     DejaVuSans.ttf
1784     DejaVuSansMono.ttf
1785     DejaVuSans-Bold.ttf
1786     DejaVuSansMono-Bold.ttf
1787     DejaVuSans-Oblique.ttf
1788     DejaVuSansMono-Oblique.ttf
1789     DejaVuSans-BoldOblique.ttf
1790     DejaVuSansMono-BoldOblique.ttf
1791     );
1792    
1793     CFClient::add_font $_ for @fonts;
1794    
1795 root 1.214 CFClient::pango_init;
1796    
1797 root 1.205 $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1798     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1799 root 1.89
1800 root 1.205 $FONT_PROP->make_default;
1801     }
1802 root 1.89
1803 root 1.219 # compare mono (ft) vs. rgba (cairo)
1804     # ft - 1.8s, cairo 3s, even in alpha-only mode
1805     # for my $rgba (0..1) {
1806     # my $t1 = Time::HiRes::time;
1807     # for (1..1000) {
1808     # my $layout = CFClient::Layout->new ($rgba);
1809     # $layout->set_text ("hallo" x 100);
1810     # $layout->render;
1811     # }
1812     # my $t2 = Time::HiRes::time;
1813     # warn $t2-$t1;
1814     # }
1815    
1816 root 1.205 video_init;
1817     audio_init;
1818 root 1.65 }
1819 root 1.40
1820 root 1.87 Event::loop;
1821 root 1.19
1822 root 1.148 END { CFClient::SDL_Quit }
1823 root 1.131
1824 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1825    
1826     Pclient is a Crossfire+ and Crossfire game client.
1827    
1828     =head2 Features
1829    
1830     =over 4
1831    
1832     =item Fullscreen Map
1833    
1834     PClient can uses a fullscreen map, which greatly enhances how much of the
1835     game world you can see.
1836    
1837     =item Persistent Map Cache (Crossfire+ only)
1838    
1839     PClient can persistently cache all map data it received from the
1840     server. This not only allows it to display an overview map, but also
1841     ensures that once-explored areas will be available the next time you want
1842     to explore more.
1843    
1844     =item Hardware acceleration
1845    
1846     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1847     acceleration. Most modern graphics cards have difficulties with 2D
1848     acceleration, while 3D graphics is accelerated well.
1849    
1850     =item No arbitrary limits
1851    
1852     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1853     limits (like a fixed amount of face numbers). There are still limits, but
1854     they are not arbitrarily low :)
1855    
1856     =back
1857    
1858 root 1.179 =head1 USAGE
1859    
1860     =head2 The Map
1861    
1862     The map is always displayed in the background, behind all other windows and UI elements.
1863    
1864     #TODO# middle-click scrolls
1865     #
1866     # keys:
1867     #
1868     # a apply
1869     # keypad moves, kp_5 applies ranged attack to self
1870    
1871     Starting to type enters the I<completion mode>. In that mode, you can type
1872     abbreviations or commands and have them executed as soon as they match a
1873     valid command. This is best explained by a few examples:
1874    
1875     Typing B<climb> will display a list of commands with I<climb> in their
1876     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1877    
1878     You can abbreviate commands by typing only the first character of every
1879 root 1.227 word (or even characters within the word - the client will try to make
1880     a good guess, as long as the characters are in order). For example,
1881     typing I<iwor> will likely select I<invoke word of recall>, while I<ccfo>
1882     will select I<cast create food>. Likewise, I<rscli> will likely select
1883     I<ready_skill climbing> and I<usl> will give you I<use_skill levitation>.
1884    
1885     You can enter space and other text as arguemnt to the command. For
1886     example, C<cfoo waybread> will expand to C<cast create food waybread>.
1887 root 1.179
1888     =head2 The map overview
1889    
1890     #TODO#
1891    
1892     =head2 The Status area in the lower right corner
1893    
1894     #TODO#
1895    
1896     =head2 The I<Statistics>/I>Stats> window
1897    
1898     #TODO#
1899    
1900 root 1.178 =head1 FAQ
1901    
1902     =over 4
1903    
1904     =item The client is very sluggish and slow, what can I do about this?
1905    
1906     Most likely, you don't have accelerated OpenGL support. Try to find a
1907     newer driver, or a driver from your hardware vendor, that features OpenGL
1908     support.
1909    
1910     If this is not an option, the following Setup options reduce the load and
1911     will likely make the client playable with sofwtare rendering (it will
1912     still be slow, though):
1913    
1914     =over 4
1915    
1916     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1917    
1918     =item Enable B<Fast & Ugly> mode
1919    
1920     =item Disable B<Fog of War>
1921    
1922     =item Increase B<Map Scale>
1923    
1924     =back
1925    
1926     =back
1927    
1928     =head1 AUTHOR
1929    
1930     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1931    
1932    
1933 root 1.82