ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.184
Committed: Tue Apr 25 11:25:20 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.183: +3 -2 lines
Log Message:
fixed font for stats

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.176 BEGIN {
7     if (%PAR::LibCache) {
8     @INC = grep ref, @INC; # weed out all paths except pars loader refs
9    
10     while (my ($filename, $zip) = each %PAR::LibCache) {
11     for ($zip->memberNames) {
12     next unless /^\/root\/(.*)/;
13     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
14     unless -e "$ENV{PAR_TEMP}/$1";
15     }
16     }
17    
18     unshift @INC, $ENV{PAR_TEMP};
19    
20     if ($^O eq "MSWin32") {
21     $ENV{GTK_RC_FILES} = "$ENV{PAR_TEMP}/share/themes/MS-Windows/gtk-2.0/gtkrc";
22     }
23     }
24     }
25    
26     # need to do it again because that pile of garbage called PAR nukes it before main
27     unshift @INC, $ENV{PAR_TEMP};
28    
29 root 1.87 use Time::HiRes 'time';
30     use Event;
31 root 1.13
32 elmex 1.11 use Crossfire;
33 root 1.2 use Crossfire::Protocol;
34    
35 root 1.116 use Compress::LZF;
36    
37 root 1.67 use CFClient;
38 root 1.72 use CFClient::UI;
39 root 1.141 use CFClient::MapWidget;
40 elmex 1.10
41 root 1.177 $Event::DIED = sub {
42     CFClient::error $_[1];
43     };
44 root 1.176
45 root 1.178 #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
46    
47 root 1.63 our $VERSION = '0.1';
48    
49 root 1.96 my $MAX_FPS = 60;
50 root 1.90 my $MIN_FPS = 5; # unused as of yet
51 root 1.63
52 root 1.112 our $META_SERVER = "crossfire.real-time.com:13326";
53    
54 root 1.116 our $FACEMAP;
55     our $TILECACHE;
56     our $MAPCACHE;
57 root 1.19
58 root 1.87 our $LAST_REFRESH;
59     our $NOW;
60    
61 elmex 1.10 our $CFG;
62 root 1.13 our $CONN;
63 root 1.85 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
64 root 1.2
65 root 1.75 our @SDL_MODES;
66 root 1.30 our $WIDTH;
67     our $HEIGHT;
68     our $FULLSCREEN;
69 root 1.99 our $FONTSIZE;
70 root 1.30
71 root 1.168 our $FONT_PROP;
72     our $FONT_FIXED;
73    
74 root 1.95 our $MAP;
75 root 1.69 our $MAPWIDGET;
76 root 1.112 our $BUTTONBAR;
77     our $LOGVIEW;
78     our $CONSOLE;
79     our $METASERVER;
80 root 1.57
81 root 1.173 our $FLOORBOX;
82 elmex 1.125 our $GAUGES;
83 elmex 1.154 our $STATWIDS;
84 elmex 1.125
85 root 1.86 our $SDL_ACTIVE;
86 root 1.13 our %SDL_CB;
87 root 1.18
88 root 1.134 our $SDL_MIXER;
89     our @SOUNDS; # event => file mapping
90     our %AUDIO_CHUNKS; # audio files
91    
92 root 1.30 our $ALT_ENTER_MESSAGE;
93 root 1.51 our $STATUS_LINE;
94 root 1.64 our $DEBUG_STATUS;
95 root 1.30
96 root 1.82 sub status {
97     $STATUS_LINE->set_text ($_[0]);
98 root 1.128 $STATUS_LINE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h} - $STATUS_LINE->{h});
99 root 1.82 }
100    
101     sub debug {
102     $DEBUG_STATUS->set_text ($_[0]);
103 root 1.128 $DEBUG_STATUS->move ($WIDTH - $DEBUG_STATUS->{w}, 0, $DEBUG_STATUS->{w}, $DEBUG_STATUS->{h});
104 root 1.82 }
105    
106 root 1.84 sub start_game {
107 root 1.85 status "logging in...";
108    
109 root 1.106 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
110 root 1.84
111 root 1.116 $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
112    
113 root 1.95 $MAP = new CFClient::Map $mapsize, $mapsize;
114 root 1.112
115     my ($host, $port) = split /:/, $CFG->{host};
116 root 1.95
117 root 1.84 $CONN = new conn
118 root 1.112 host => $host,
119     port => $port || 13327,
120 root 1.84 user => $CFG->{user},
121     pass => $CFG->{password},
122     mapw => $mapsize,
123     maph => $mapsize,
124     ;
125    
126 root 1.85 status "login successful";
127    
128 root 1.84 CFClient::lowdelay fileno $CONN->{fh};
129     }
130    
131     sub stop_game {
132     undef $CONN;
133     }
134    
135 root 1.111 sub client_setup {
136 root 1.99 my $dialog = new CFClient::UI::FancyFrame
137 root 1.150 title => "Client Setup",
138 root 1.81 child => (my $vbox = new CFClient::UI::VBox);
139     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
140    
141 root 1.140 $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
142 root 1.81 $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
143    
144     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, scalar @SDL_MODES, 1]);
145 root 1.150 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
146 root 1.81
147     $mode_slider->connect (changed => sub {
148     my ($self, $value) = @_;
149    
150     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
151     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
152     });
153     $mode_slider->emit (changed => $mode_slider->{range}[0]);
154 root 1.82
155 elmex 1.158 my $row = 1;
156    
157     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
158 elmex 1.166 $table->add (1, $row++, new CFClient::UI::CheckBox
159     state => $CFG->{fullscreen},
160     tooltip => "Bring the client into fullscreen mode",
161     connect_changed => sub {
162     my ($self, $value) = @_;
163     $CFG->{fullscreen} = $value;
164     }
165     );
166 root 1.85
167 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
168 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
169     state => $CFG->{fast},
170     tooltip => "Lower the visual quality considerably to speed up rendering.",
171     connect_changed => sub {
172     my ($self, $value) = @_;
173     $CFG->{fast} = $value;
174     }
175     );
176 root 1.89
177 root 1.169 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
178     $table->add (1, $row++, new CFClient::UI::Slider
179     range => [$CFG->{map_scale}, 0.25, 2, 0.05],
180     tooltip => "Enlarge or shrink the displayed map",
181     connect_changed => sub {
182     my ($self, $value) = @_;
183     $CFG->{map_scale} = 0.05 * int $value / 0.05;
184     }
185     );
186    
187 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
188 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
189     state => $CFG->{fow_enable},
190     tooltip => "Fog-of-War marks areas that cannot be seen by the player",
191     connect_changed => sub {
192     my ($self, $value) = @_;
193     $CFG->{fow_enable} = $value;
194     }
195     );
196 root 1.97
197 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
198 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
199     range => [$CFG->{fow_intensity}, 0, 1 + 0.001, 0.001],
200     tooltip => "The higher the intensity, the lighter the Fog-of-War color",
201     connect_changed => sub {
202     my ($self, $value) = @_;
203     $CFG->{fow_intensity} = $value;
204     }
205     );
206 root 1.90
207 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
208 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
209     state => $CFG->{fow_smooth},
210     tooltip => "Smooth the Fog-of-War a bit to make it more realistic",
211     connect_changed => sub {
212     my ($self, $value) = @_;
213     $CFG->{fow_smooth} = $value;
214     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
215     }
216     );
217 root 1.91
218 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
219 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
220     range => [$CFG->{gui_fontsize}, 0.5, 2, 0.1],
221     tooltip => "The font size used by most GUI elements",
222     connect_changed => sub {
223     $CFG->{gui_fontsize} = 0.1 * int $_[1] * 10;
224 root 1.140 # $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
225 root 1.163 }
226     );
227 root 1.140
228 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Server Log Fontsize");
229 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
230     range => [$CFG->{log_fontsize}, 0.5, 2, 0.1],
231     tooltip => "The font size used by the server log window only",
232     connect_changed => sub {
233     $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = 0.1 * int $_[1] * 10);
234     }
235     );
236 root 1.105
237 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
238 root 1.163
239     $table->add (1, $row++, new CFClient::UI::Slider
240     range => [$CFG->{stat_fontsize}, 0.5, 2, 0.1],
241     tooltip => "The font size used by the statistics window only",
242     connect_changed => sub {
243     $CFG->{stat_fontsize} = 0.1 * int $_[1] * 10;
244     &set_stats_window_fontsize;
245     }
246     );
247 elmex 1.157
248 root 1.163 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
249     $table->add (1, $row++, new CFClient::UI::Slider
250     range => [$CFG->{gauge_size}, 0.2, 0.8, 0.02],
251     tooltip => "Adjust the size of the stats gauges at the bottom right",
252     connect_changed => sub {
253     $CFG->{gauge_size} = $_[1];
254 root 1.164 my $h = int $HEIGHT * $CFG->{gauge_size};
255 root 1.163 $GAUGES->{win}->set_size ($WIDTH, $h);
256 root 1.164 $GAUGES->{win}->move (0, $HEIGHT - $h);
257 root 1.163 }
258     );
259 elmex 1.158
260     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
261 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
262     range => [$CFG->{gauge_fontsize}, 0.5, 2.0, 0.1],
263 elmex 1.166 tooltip => "Adjusts the fontsize of the gauges at the bottom right",
264 root 1.163 connect_changed => sub {
265     $CFG->{gauge_fontsize} = 0.1 * int $_[1] * 10;
266     &set_gauge_window_fontsize;
267     }
268     );
269 elmex 1.158
270 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
271     expand => 1, align => 0, text => "Apply",
272 root 1.168 tooltip => "Apply the video settings",
273 root 1.163 connect_activate => sub {
274     video_shutdown ();
275     video_init ();
276     }
277     );
278 root 1.111
279 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
280 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
281     state => $CFG->{audio_enable},
282     tooltip => "If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
283     connect_changed => sub {
284     $CFG->{audio_enable} = $_[1];
285     }
286     );
287 root 1.140 # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
288     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
289     # $CFG->{effects_volume} = $_[1];
290     # });
291 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
292     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
293 root 1.163 $hbox->add (new CFClient::UI::CheckBox
294     expand => 1, state => $CFG->{bgm_enable},
295     tooltip => "Enable background music playing",
296     connect_changed => sub {
297     $CFG->{bgm_enable} = $_[1];
298     }
299     );
300     $hbox->add (new CFClient::UI::Slider
301     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0.1],
302     tooltip => "The volume of the background music",
303     connect_changed => sub {
304     $CFG->{bgm_volume} = $_[1];
305     CFClient::MixMusic::volume $_[1] * 128;
306     }
307     );
308 root 1.140
309 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
310     expand => 1, align => 0, text => "Apply",
311 root 1.168 tooltip => "Apply the audio settings",
312 root 1.163 connect_activate => sub {
313     audio_shutdown ();
314     audio_init ();
315     }
316     );
317 elmex 1.137
318 root 1.111 $dialog
319     }
320    
321 elmex 1.157 sub set_stats_window_fontsize {
322 elmex 1.158 for (values %{$STATWIDS}) {
323 elmex 1.157 $_->set_fontsize ($::CFG->{stat_fontsize});
324     }
325     }
326    
327 elmex 1.158 sub set_gauge_window_fontsize {
328     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
329     $_->set_fontsize ($::CFG->{gauge_fontsize});
330     }
331 root 1.169
332     # local $GAUGES->{win}{parent};#d#
333     # use PApp::Util; open D, ">:utf8", "d"; print D PApp::Util::dumpval $GAUGES->{win}; close D;
334 elmex 1.158 }
335    
336     sub make_gauge_window {
337     my $gh = int ($HEIGHT * $CFG->{gauge_size});
338 elmex 1.161 # my $gw = int ($WIDTH * $CFG->{gauge_w_size});
339 elmex 1.158
340     my $win = new CFClient::UI::Frame (
341 root 1.169 y => $HEIGHT - $gh, x => 0, user_w => $WIDTH, user_h => $gh
342 elmex 1.158 );
343 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
344     children => [
345     (new CFClient::UI::HBox expand => 1),
346     ($FLOORBOX = new CFClient::UI::VBox),
347     (my $vbox = new CFClient::UI::VBox),
348     ],
349     );
350 elmex 1.158
351 root 1.173 $vbox->add (new CFClient::UI::HBox
352     expand => 1,
353     children => [
354     (new CFClient::UI::Empty expand => 1),
355     (my $hb = new CFClient::UI::HBox),
356     ],
357     );
358 elmex 1.161
359 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
360 root 1.181 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.");
361 root 1.172 $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
362 root 1.181 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.");
363 root 1.172 $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
364 root 1.181 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.");
365 root 1.172 $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
366 root 1.181 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.");
367 root 1.172
368 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
369 root 1.182 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.");
370 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
371 root 1.172 tooltip => "Ranged attack - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
372 elmex 1.158
373     $GAUGES = {
374 elmex 1.166 exp => $exp, win => $win, range => $rng,
375 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
376     };
377 root 1.169
378     &set_gauge_window_fontsize;
379    
380 elmex 1.158 $win
381     }
382    
383 elmex 1.154 sub make_stats_window {
384 root 1.183 my $tgw = new CFClient::UI::FancyFrame x => $WIDTH * 2/5, y => 0, title => "Stats";
385 root 1.155
386     $tgw->add (my $vb = new CFClient::UI::VBox);
387 root 1.168 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1);
388     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1);
389 elmex 1.156
390     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
391    
392     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
393    
394 root 1.174 my $black = [0, 0, 0];
395    
396 root 1.180 for (
397     [0, 0, st_str => "Str", 30, "Physical Strength, determines damage dealt with weapons, how much you can carry, and how often you can attack"],
398     [0, 1, st_dex => "Dex", 30, "Dexterity, your physical agility. Determines chance of being hit and affects armor class and speed"],
399     [0, 2, st_con => "Con", 30, "Constitution, physical health and toughness. Determines how many healthpoints you can have"],
400     [0, 3, st_int => "Int", 30, "Intelligence, your ability to learn and use skills and incantations (both prayers and magic) and determines how much spell points you can have"],
401     [0, 4, st_wis => "Wis", 30, "Wisdom, the ability to learn and use divine magic (prayers). Determines how many grace points you can have"],
402     [0, 5, st_pow => "Pow", 30, "Power, your magical potential. Influences the strength of spell effects, and also how much your spell and grace points increase when leveling up"],
403     [0, 6, st_cha => "Cha", 30, "Charisma, how well you are received by NPCs. Affects buying and selling prices in shops."],
404    
405     [2, 0, st_wc => "Wc", -120, "Weapon Class, 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."],
406     [2, 1, st_ac => "Ac", -120, "Armour Class, 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."],
407     [2, 2, st_dam => "Dam", 120, "Damage, how much damage your melee/missile attack inflicts. Higher values indicate a greater amount of damage will be inflicted with each attack."],
408     [2, 3, st_arm => "Arm", 120, "Armour, 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."],
409     [2, 4, st_spd => "Spd", 10.54, "Speed, 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."],
410     [2, 5, st_wspd => "WSp", 10.54, "Weapon Speed, 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."],
411     ) {
412     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
413    
414     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
415 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
416 root 1.180 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
417 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $black, valign => 0, align => -1, text => $label, tooltip => $tooltip);
418 root 1.180 }
419 root 1.155
420 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
421 root 1.155
422 elmex 1.156 my $row = 0;
423     my $col = 0;
424 root 1.155
425 elmex 1.166 my %resist_names = (
426     slow => "Slow",
427     holyw => "Holy Word",
428     conf => "Confusion",
429     fire => "Fire",
430 root 1.180 depl => "Depletion (some monsters and other effects can cause stats depletion)",
431 elmex 1.166 magic => "Magic",
432 root 1.180 drain => "Draining (some monsters (e.g. vampires) and other effects can steal experience)",
433 elmex 1.166 acid => "Acid",
434     pois => "Poison",
435     para => "Paralysation",
436 root 1.180 deat => "Death (resistance against death spells)",
437 elmex 1.166 phys => "Physical",
438     blind => "Blind",
439     fear => "Fear",
440     tund => "Turn undead",
441     elec => "Electricity",
442     cold => "Cold",
443 root 1.180 ghit => "Ghost hit (special attack used by ghosts and ghost-like beings)",
444 elmex 1.166 );
445 elmex 1.156 for (qw/slow holyw conf fire depl magic
446     drain acid pois para deat phys
447     blind fear tund elec cold ghit/)
448     {
449 root 1.164 $tbl2->add ($col, $row,
450 elmex 1.156 $STATWIDS->{"res_$_"} =
451 root 1.168 new CFClient::UI::Label
452 root 1.184 font => $FONT_FIXED,
453 root 1.180 template => "-100%",
454     align => +1,
455     valign => 0,
456     can_events => 1,
457     can_hover => 1,
458     tooltip => $resist_names{$_},
459 root 1.168 );
460     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
461 root 1.180 can_hover => 1,
462 root 1.168 can_events => 1,
463 root 1.180 image => "ui/resist/resist_$_.png",
464     tooltip => $resist_names{$_},
465 elmex 1.156 );
466    
467     $row++;
468     if ($row % 6 == 0) {
469     $col += 2;
470     $row = 0;
471     }
472     }
473    
474 elmex 1.157 &set_stats_window_fontsize;
475 elmex 1.156 update_stats_window ({});
476 root 1.155
477 elmex 1.154 $tgw
478     }
479    
480 root 1.169 sub formsep {
481     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
482     }
483    
484 elmex 1.154 sub update_stats_window {
485     my ($stats) = @_;
486    
487 elmex 1.156 # i love text protocols!!!
488 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
489     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
490     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
491     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
492     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
493 elmex 1.156 my $fo_m = 999;
494 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
495     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
496 elmex 1.156
497     $GAUGES->{hp} ->set_value ($hp, $hp_m);
498     $GAUGES->{mana} ->set_value ($sp, $sp_m);
499     $GAUGES->{food} ->set_value ($fo, $fo_m);
500     $GAUGES->{grace} ->set_value ($gr, $gr_m);
501 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
502     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
503     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
504 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
505     $GAUGES->{range} ->set_text ("Rng: " . $rng);
506 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
507 elmex 1.165 $title =~ s/^Player: //;
508     $STATWIDS->{title} ->set_text ("Title: " . $title);
509 elmex 1.156
510 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
511     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
512     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
513     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
514     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
515     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
516     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
517     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
518     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
519     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
520     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
521     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
522     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
523 elmex 1.156
524     my %tbl = (
525     phys => 100,
526     magic => 101,
527     fire => 102,
528     elec => 103,
529     cold => 104,
530     conf => 105,
531     acid => 106,
532     drain => 107,
533     ghit => 108,
534     pois => 109,
535     slow => 110,
536     para => 111,
537     tund => 112,
538     fear => 113,
539 elmex 1.165 depl => 113,
540 elmex 1.156 deat => 115,
541     holyw => 116,
542     blind => 117
543 elmex 1.154 );
544 elmex 1.156
545     for (keys %tbl) {
546     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
547     }
548    
549 elmex 1.154 }
550    
551 root 1.112 sub metaserver_dialog {
552     my $dialog = new CFClient::UI::FancyFrame
553 root 1.150 title => "Metaserver",
554 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
555    
556     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
557    
558     $dialog
559     }
560    
561 root 1.179 my $METASERVER_ATIME;
562    
563 root 1.112 sub update_metaserver {
564 root 1.114 my ($HOST) = @_;
565    
566 root 1.179 return if $METASERVER_ATIME > time;
567     $METASERVER_ATIME = time + 60;
568    
569 root 1.178 my $table = $METASERVER->{table};
570     $table->clear;
571 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
572 root 1.112
573     my $buf;
574    
575     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
576    
577 root 1.178 unless ($fh) {
578     $label->set_text ("unable to contact metaserver: $!");
579     return;
580     }
581    
582 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
583     my $res = sysread $fh, $buf, 8192, length $buf;
584    
585     if (!defined $res) {
586     $_[0]->w->cancel;
587 root 1.178 $label->set_text ("error while retrieving server list: $!");
588 root 1.112 } elsif ($res == 0) {
589     $_[0]->w->cancel;
590     status "server list retrieved";
591 root 1.113
592 root 1.178 utf8::decode $buf if utf8::valid $buf;
593 root 1.113
594     $table->clear;
595    
596 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
597 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
598     for 0 .. $#col;
599    
600     my @align = qw(1 0 1 1 -1);
601    
602     my $y = 0;
603 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
604 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
605    
606     for ($desc) {
607     s/<br>/\n/gi;
608     s/<li>/\n· /gi;
609     s/<.*?>//sgi;
610     s/&/&amp;/g;
611     s/</&lt;/g;
612     s/>/&gt;/g;
613     }
614    
615     $uptime = sprintf "%dd %02d:%02d:%02d",
616     (int $m->[8] / 86400),
617     (int $m->[8] / 3600) % 24,
618     (int $m->[8] / 60) % 60,
619     $m->[8] % 60;
620    
621     $m = [$users, $host, $uptime, $version, $desc];
622    
623     $y++;
624 root 1.114
625     $table->add (0, $y, new CFClient::UI::VBox children => [
626 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
627 root 1.114 $HOST->set_text ($CFG->{host} = $host);
628     }),
629     (new CFClient::UI::Empty expand => 1),
630     ]);
631    
632 root 1.140 $table->add ($_ + 1, $y, new CFClient::UI::Label align => $align[$_], text => $m->[$_], fontsize => 0.8)
633 root 1.113 for 0 .. $#$m;
634     }
635 root 1.112 }
636     });
637     }
638    
639 root 1.111 sub server_setup {
640     my $dialog = new CFClient::UI::FancyFrame
641 root 1.150 title => "Server Setup",
642 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
643 root 1.81
644 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
645 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
646 root 1.112
647     {
648     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
649    
650 elmex 1.166 $vbox->add (
651     my $HOST = new CFClient::UI::Entry
652     expand => 1,
653     text => $CFG->{host},
654     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
655     connect_changed => sub {
656     my ($self, $value) = @_;
657     $CFG->{host} = $value;
658     }
659     );
660 root 1.112
661     $METASERVER = metaserver_dialog;
662 elmex 1.101
663 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
664     expand => 1,
665     text => "Metaserver",
666     other => $METASERVER,
667 root 1.167 tooltip => "Show a list of avaible crossfire servers",
668 elmex 1.166 connect_open => sub {
669     update_metaserver $HOST;
670     }
671     );
672 root 1.112 }
673 root 1.81
674 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
675 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
676     text => $CFG->{user},
677     tooltip => "The name of your character on the server",
678     connect_changed => sub {
679     my ($self, $value) = @_;
680     $CFG->{user} = $value;
681     }
682     );
683 root 1.81
684 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
685 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
686     text => $CFG->{password},
687     hidden => 1,
688     tooltip => "The password for your character",
689     connect_changed => sub {
690     my ($self, $value) = @_;
691     $CFG->{password} = $value;
692     }
693     );
694 elmex 1.101
695 root 1.141 $table->add (0, 6, new CFClient::UI::Label valign => 0, align => 1, text => "Def. say cmd");
696 elmex 1.166 $table->add (1, 6, my $saycmd = new CFClient::UI::Entry
697     text => $CFG->{say_command},
698     tooltip => "This is the command that will be used if you write a line in the message window entry. "
699     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
700 root 1.173 ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
701 elmex 1.166 connect_changed => sub {
702     my ($self, $value) = @_;
703     $CFG->{say_command} = $value;
704     }
705     );
706 root 1.81
707 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
708 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
709 root 1.81 req_w => 100,
710     range => [$CFG->{mapsize}, 10, 100 + 1, 1],
711 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
712     ."If you set this to a high value you will be able to see further for example.",
713 root 1.81 connect_changed => sub {
714     my ($self, $value) = @_;
715    
716     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
717     },
718     );
719    
720 elmex 1.101 $table->add (1, 8, new CFClient::UI::Button expand => 1, align => 0, text => "Login", connect_activate => sub {
721 root 1.84 start_game;
722 root 1.82 });
723    
724 root 1.98 $dialog
725 root 1.81 }
726 root 1.58
727 root 1.111 sub message_window {
728 root 1.99 my $window = new CFClient::UI::FancyFrame
729 root 1.150 title => "Messages",
730 root 1.99 border_bg => [1, 1, 1, 0.5],
731     bg => [0.3, 0.3, 0.3, 0.8],
732 root 1.124 user_w => int $::WIDTH / 3,
733     user_h => int $::HEIGHT / 5,
734 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
735    
736 root 1.105 $vbox->add ($LOGVIEW = new CFClient::UI::TextView
737     expand => 1,
738 root 1.168 font => $FONT_FIXED,
739 root 1.105 fontsize => $::CFG->{log_fontsize},
740     );
741    
742 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
743 elmex 1.118 connect_focus_in => sub {
744     my ($input, $prev_focus) = @_;
745    
746     delete $input->{refocus_map};
747    
748     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
749     $input->{refocus_map} = 1;
750     }
751     delete $input->{auto_activated};
752     },
753 root 1.116 connect_activate => sub {
754 elmex 1.100 my ($input, $text) = @_;
755     $input->set_text ('');
756    
757     if ($text =~ /^\/(.*)/) {
758 root 1.123 $::CONN->user_send ($1);
759 elmex 1.100 } else {
760 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
761 root 1.123 $::CONN->user_send ("$say_cmd $text");
762 elmex 1.100 }
763 elmex 1.118 if ($input->{refocus_map}) {
764     delete $input->{refocus_map};
765     $MAPWIDGET->focus_in
766     }
767 root 1.116 },
768     connect_escape => sub {
769 elmex 1.102 $MAPWIDGET->focus_in
770 root 1.116 },
771     );
772 elmex 1.102
773     $CONSOLE = {
774     window => $window,
775     input => $input
776     };
777 root 1.99
778     $window
779     }
780    
781 root 1.89 sub sdl_init {
782 root 1.145 CFClient::SDL_Init
783 root 1.89 and die "SDL::Init failed!\n";
784     }
785    
786 root 1.134 sub video_init {
787 root 1.89 sdl_init;
788    
789 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
790     $FULLSCREEN = $CFG->{fullscreen};
791 root 1.89 $FAST = $CFG->{fast};
792 root 1.84
793 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
794     or die "SDL_SetVideoMode failed!\n";
795 root 1.2
796 root 1.86 $SDL_ACTIVE = 1;
797    
798 root 1.87 $LAST_REFRESH = time - 0.01;
799 root 1.45
800 root 1.67 CFClient::gl_init;
801 root 1.30
802 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
803 root 1.39
804 root 1.52 #############################################################################
805    
806 root 1.99 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100;
807 root 1.140 $DEBUG_STATUS->show;
808 root 1.52
809 root 1.72 $STATUS_LINE = new CFClient::UI::Label
810 root 1.77 padding => 0,
811 root 1.140 y => $HEIGHT - $FONTSIZE * 1.8;
812     $STATUS_LINE->show;
813 root 1.51
814 root 1.72 $ALT_ENTER_MESSAGE = new CFClient::UI::Label
815 root 1.123 padding => 0,
816 root 1.140 fontsize => 0.8,
817 root 1.123 markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode";
818 root 1.140 $ALT_ENTER_MESSAGE->show;
819     $ALT_ENTER_MESSAGE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h});
820 root 1.30
821 root 1.141 $CFClient::UI::ROOT->add ($MAPWIDGET = new CFClient::MapWidget);
822 root 1.69 $MAPWIDGET->focus_in;
823 elmex 1.102 $MAPWIDGET->connect (activate_console => sub {
824 elmex 1.103 my ($mapwidget, $preset) = @_;
825    
826 elmex 1.102 if ($CONSOLE) {
827 elmex 1.118 $CONSOLE->{input}->{auto_activated} = 1;
828 elmex 1.102 $CONSOLE->{input}->focus_in;
829 elmex 1.103
830     if ($preset && $CONSOLE->{input}->get_text eq '') {
831     $CONSOLE->{input}->set_text ($preset);
832     }
833 elmex 1.102 }
834     });
835 root 1.81
836 root 1.111 $CFClient::UI::ROOT->add ($BUTTONBAR = new CFClient::UI::HBox);
837    
838     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup);
839     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup);
840     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window);
841    
842 root 1.167 $CFClient::UI::ROOT->add (make_gauge_window); # 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
843     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window);
844    
845 root 1.111 $BUTTONBAR->add (new CFClient::UI::Button text => "Save Config", connect_activate => sub {
846     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
847     status "Configuration Saved";
848     });
849 root 1.98
850 root 1.119 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
851 root 1.2 }
852    
853 root 1.134 sub video_shutdown {
854 root 1.111 $CFClient::UI::ROOT->{children} = [];
855 root 1.177 undef $CFClient::UI::GRAB;
856     undef $CFClient::UI::HOVER;
857 root 1.86 undef $SDL_ACTIVE;
858 root 1.134 }
859    
860 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#
861 root 1.135 my $bgmusic;#TODO#hack#d#
862    
863 root 1.153 sub audio_music_finished {
864     return unless $CFG->{bgm_enable};
865    
866     # TODO: hack, do play loop and mood music
867     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
868     $bgmusic->play (0);
869    
870     push @bgmusic, shift @bgmusic;
871     }
872    
873 root 1.134 sub audio_init {
874 root 1.139 if ($CFG->{audio_enable}) {
875 root 1.134 if (open my $fh, "<:utf8", CFClient::find_rcfile "sounds/config") {
876 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
877     CFClient::Mix_AllocateChannels 8;
878 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
879 root 1.134
880 root 1.153 audio_music_finished;
881 root 1.135
882 root 1.134 while (<$fh>) {
883     next if /^\s*#/;
884     next if /^\s*$/;
885    
886     my ($file, $volume, $event) = split /\s+/, $_, 3;
887    
888     push @SOUNDS, "$volume,$file";
889    
890     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
891 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
892 root 1.134 $chunk->volume ($volume * 128 / 100);
893     $chunk
894     };
895     }
896     } else {
897     status "unable to open sound config: $!";
898     }
899     }
900     }
901    
902     sub audio_shutdown {
903 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
904 root 1.134 undef $SDL_MIXER;
905     @SOUNDS = ();
906     %AUDIO_CHUNKS = ();
907 root 1.62 }
908    
909 root 1.87 my %animate_object;
910     my $animate_timer;
911    
912     my $want_refresh;
913     my $can_refresh;
914    
915     my $fps = 9;
916    
917 root 1.30 sub force_refresh {
918 root 1.87 $fps = $fps * 0.95 + 1 / ($NOW - $LAST_REFRESH) * 0.05;
919     debug sprintf "%3.2f", $fps;
920    
921 root 1.96 $want_refresh = 0;
922 root 1.87 $can_refresh = 0;
923    
924 root 1.111 $CFClient::UI::ROOT->draw;
925 root 1.1
926 root 1.148 CFClient::SDL_GL_SwapBuffers;
927 root 1.87
928     $LAST_REFRESH = $NOW;
929 root 1.1 }
930    
931 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
932     $NOW = time;
933    
934 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
935     for CFClient::SDL_PollEvent;
936 root 1.87
937     if (%animate_object) {
938     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
939     $want_refresh++;
940     }
941    
942     if ($want_refresh) {
943     force_refresh;
944     } else {
945     $can_refresh = 1;
946     }
947     });
948 root 1.64
949 root 1.30 sub refresh {
950 root 1.87 $want_refresh++;
951 root 1.30 }
952    
953 root 1.45 sub animation_start {
954     my ($widget) = @_;
955 root 1.87 $animate_object{$widget} = $widget;
956 root 1.45 }
957    
958     sub animation_stop {
959     my ($widget) = @_;
960 root 1.87 delete $animate_object{$widget};
961 root 1.45 }
962    
963 root 1.2 @conn::ISA = Crossfire::Protocol::;
964 root 1.1
965 elmex 1.125 sub conn::stats_update {
966     my ($self, $stats) = @_;
967    
968 elmex 1.154 update_stats_window ($stats);
969 elmex 1.125 }
970    
971 root 1.89 sub conn::user_send {
972 root 1.88 my ($self, $command) = @_;
973    
974 root 1.123 $self->send_command ($command);
975 root 1.88 status $command;
976     }
977    
978 root 1.119 sub conn::map_scroll {
979     my ($self, $dx, $dy) = @_;
980    
981     $MAP->scroll ($dx, $dy);
982     }
983    
984 root 1.94 sub conn::feed_map1a {
985     my ($self, $data) = @_;
986    
987 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
988 root 1.1
989 root 1.95 $MAP->map1a_update ($data);
990 root 1.69 $MAPWIDGET->update;
991 root 1.1 }
992    
993 root 1.116 sub conn::flush_map {
994     my ($self) = @_;
995    
996     my $map_info = delete $self->{map_info}
997     or return;
998    
999     my ($hash, $x, $y, $w, $h) = @$map_info;
1000    
1001     my $data = $MAP->get_rect ($x, $y, $w, $h);
1002     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1003 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1004 root 1.116 }
1005 root 1.1
1006 root 1.2 sub conn::map_clear {
1007 root 1.1 my ($self) = @_;
1008    
1009 root 1.116 $self->flush_map;
1010 root 1.150 delete $self->{neigh_map};
1011 root 1.116
1012 root 1.95 $MAP->clear;
1013 root 1.1 }
1014    
1015 root 1.116
1016 root 1.119 sub conn::load_map($$$) {
1017     my ($self, $hash, $x, $y) = @_;
1018 root 1.115
1019 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1020     $data = Compress::LZF::decompress $data;
1021 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1022 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1023     my $data = $TILECACHE->get ($id)
1024     or next;
1025    
1026     $self->set_texture ($id => $data);
1027     }
1028     }
1029 root 1.115 }
1030    
1031 root 1.152 # this method does a "flood fill" into every tile direction
1032     # it assumes that tiles are arranged in a rectangular grid,
1033     # i.e. a map is the same as the left of the right map etc.
1034     # failure to comply are harmless and result in display errors
1035     # at worst.
1036 root 1.119 sub conn::flood_fill {
1037 root 1.150 my ($self, $gx, $gy, $path, $hash, $flags) = @_;
1038 root 1.119
1039 root 1.121 # the server does not allow map paths > 6
1040 root 1.120 return if 6 <= length $path;
1041    
1042 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1043    
1044     for (
1045     [1, 0, -1],
1046     [2, 1, 0],
1047     [3, 0, 1],
1048     [4, -1, 0],
1049     ) {
1050     my ($tile, $dx, $dy) = @$_;
1051    
1052     my $gx = $gx + $dx;
1053     my $gy = $gy + $dy;
1054    
1055 root 1.119 next unless $flags & (1 << ($tile - 1));
1056 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1057 root 1.119
1058 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1059     if (my $info = $neigh->[$tile]) {
1060     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1061 root 1.119
1062 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1063     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1064    
1065     } else {
1066     $self->send_mapinfo ("spatial $path$tile", sub {
1067     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1068 root 1.119
1069 root 1.150 return if $mode ne "spatial";
1070 root 1.119
1071 root 1.150 $x += $MAP->ox;
1072     $y += $MAP->oy;
1073    
1074     $self->load_map ($hash, $x, $y)
1075     unless $self->{neigh_map}{$hash}[5]++;#d#
1076 root 1.119
1077 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1078 root 1.119
1079 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1080     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1081     });
1082     }
1083 root 1.119 }
1084     }
1085    
1086     sub conn::map_change {
1087     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1088    
1089     $self->flush_map;
1090    
1091     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1092    
1093     my $mapmapw = 250;
1094     my $mapmaph = 250;
1095 root 1.150
1096     $self->{neigh_rect} = [
1097 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1098     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1099 root 1.150 ];
1100 root 1.119
1101 root 1.150 delete $self->{neigh_grid};
1102     $self->flood_fill (0, 0, "", $hash, $flags);
1103 root 1.119
1104     $x += $ox;
1105     $y += $oy;
1106    
1107     $self->{map_info} = [$hash, $x, $y, $w, $h];
1108    
1109 elmex 1.158 my $map = $self->{map_info}[0];
1110     $map =~ s/^.*?\/([^\/]+)$/\1/;
1111     $STATWIDS->{map}->set_text ("Map: " . $map);
1112 elmex 1.157
1113 root 1.119 $self->load_map ($hash, $x, $y);
1114     }
1115    
1116 root 1.19 sub conn::face_find {
1117 root 1.116 my ($self, $facenum, $face) = @_;
1118    
1119     my $hash = "$face->{chksum},$face->{name}";
1120    
1121     my $id = $FACEMAP->get ($hash);
1122    
1123     unless ($id) {
1124     # create new id for face
1125     # i love transactions
1126     for (1..100) {
1127     my $txn = $CFClient::DB_ENV->txn_begin;
1128     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1129     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1130     $id++;
1131     if ($FACEMAP->put (id => $id) == 0
1132     && $FACEMAP->put ($hash => $id) == 0) {
1133     $txn->txn_commit;
1134    
1135     goto gotid;
1136     }
1137     }
1138     $txn->abort;
1139     }
1140 root 1.19
1141 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1142     }
1143 root 1.114
1144 root 1.116 gotid:
1145     $face->{id} = $id;
1146     $MAP->set_face ($facenum => $id);
1147 root 1.173 $self->{faceid}[$facenum] = $id;#d#
1148 root 1.116 $TILECACHE->get ($id)
1149 root 1.19 }
1150    
1151 root 1.2 sub conn::face_update {
1152 root 1.95 my ($self, $facenum, $face) = @_;
1153 root 1.19
1154 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1155    
1156     $self->set_texture ($face->{id} => delete $face->{image});
1157     }
1158 root 1.1
1159 root 1.116 sub conn::set_texture {
1160     my ($self, $id, $data) = @_;
1161 root 1.95
1162 root 1.116 $self->{texture}[$id] ||= do {
1163     my $tex =
1164     new_from_image CFClient::Texture
1165 root 1.173 $data, minify => 1, mipmap => 1;
1166 root 1.116
1167     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1168     $MAPWIDGET->update;
1169    
1170     $tex
1171     };
1172 root 1.1 }
1173    
1174 root 1.134 sub conn::sound_play {
1175     my ($self, $x, $y, $soundnum, $type) = @_;
1176    
1177 root 1.139 $SDL_MIXER
1178     or return;
1179    
1180 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1181     or return;
1182    
1183 root 1.146 $chunk->play;
1184 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1185 root 1.134 }
1186    
1187 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1188    
1189 root 1.33 sub conn::query {
1190     my ($self, $flags, $prompt) = @_;
1191    
1192 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1193     $LAST_QUERY = $prompt;
1194    
1195     my $dialog = new CFClient::UI::FancyFrame
1196     title => "Query",
1197     child => my $vbox = new CFClient::UI::VBox;
1198    
1199     $vbox->add (new CFClient::UI::Label
1200     max_w => $::WIDTH * 0.4,
1201     text => $prompt);
1202    
1203     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1204     $vbox->add (my $hbox = new CFClient::HBox);
1205     $hbox->add (new CFClient::Button
1206     text => "No",
1207     connect_activate => sub {
1208     $self->send ("reply n");
1209     $dialog->destroy;
1210     $MAPWIDGET->focus_in;
1211     }
1212     );
1213     $hbox->add (new CFClient::Button
1214     text => "Yes",
1215     connect_activate => sub {
1216     $self->send ("reply y");
1217     $dialog->destroy;
1218     $MAPWIDGET->focus_in;
1219     },
1220     );
1221    
1222     $dialog->focus_in;
1223    
1224     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1225     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1226     $vbox->add (my $entry = new CFClient::UI::Entry
1227     connect_changed => sub {
1228     $self->send ("reply $_[1]");
1229     $dialog->destroy;
1230     $MAPWIDGET->focus_in;
1231     },
1232     );
1233    
1234     $entry->focus_in;
1235    
1236     } else {
1237     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1238    
1239     $vbox->add (my $entry = new CFClient::UI::Entry
1240     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1241     connect_activate => sub {
1242     $self->send ("reply $_[1]");
1243     $dialog->destroy;
1244     $MAPWIDGET->focus_in;
1245     },
1246     );
1247    
1248     $entry->focus_in;
1249     }
1250    
1251     $dialog->show;
1252 root 1.33 }
1253    
1254 root 1.99 sub conn::drawinfo {
1255     my ($self, $color, $text) = @_;
1256    
1257     my @color = (
1258     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1259     [1.00, 1.00, 1.00],
1260 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1261 root 1.99 [1.00, 0.00, 0.00],
1262     [1.00, 0.54, 0.00],
1263     [0.11, 0.56, 1.00],
1264     [0.93, 0.46, 0.00],
1265     [0.18, 0.54, 0.34],
1266     [0.56, 0.73, 0.56],
1267     [0.80, 0.80, 0.80],
1268     [0.55, 0.41, 0.13],
1269     [0.99, 0.77, 0.26],
1270     [0.74, 0.65, 0.41],
1271     );
1272    
1273     $LOGVIEW->add_paragraph ($color[$color], $text);
1274     }
1275    
1276 root 1.144 sub conn::spell_add {
1277 root 1.143 my ($self, $spell) = @_;
1278    
1279 root 1.171 # TODO
1280     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1281     $MAPWIDGET->add_command ("invoke $spell->{name}", $spell->{message});
1282     $MAPWIDGET->add_command ("cast $spell->{name}", $spell->{message});
1283 root 1.144 }
1284    
1285     sub conn::spell_delete {
1286     my ($self, $spell) = @_;
1287     }
1288    
1289     sub conn::addme_success {
1290     my ($self) = @_;
1291    
1292     for my $skill (values %{$self->{skill_info}}) {
1293 root 1.171 $MAPWIDGET->add_command ("ready_skill $skill", "Ready the skill '$skill'");
1294     $MAPWIDGET->add_command ("use_skill $skill", "Immediately use the skill '$skill'");
1295 root 1.144 }
1296 root 1.143 }
1297    
1298 root 1.173 sub update_floorbox {
1299     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1300     $FLOORBOX->clear;
1301     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1302    
1303     my @items = values %{ $CONN->{container}{0} };
1304    
1305     # we basically have to use the same sorting as everybody else
1306     @items = sort { $a->{type} <=> $b->{type} } @items;
1307    
1308     for my $item (reverse @items) {
1309     my $desc = $item->{nrof} < 2
1310     ? $item->{name}
1311     : "$item->{nrof} $item->{name_pl}";
1312     # todo: animation widget, face widget, weight(?) etc.
1313     $FLOORBOX->add (my $hbox = new CFClient::UI::HBox
1314     tooltip => (CFClient::UI::Label->escape ($desc)
1315     . "\n<small>leftclick - pick up\nmiddle click - apply\nrightclick - menu</small>"),
1316     can_hover => 1,
1317     can_events => 1,
1318 root 1.174 connect_button_down => sub {
1319     my ($self, $ev, $x, $y) = @_;
1320    
1321     # todo: maybe put examine on 1? but should just be a tooltip :(
1322     if ($ev->{button} == 1) {
1323     $CONN->send ("move $CONN->{player}{tag} $item->{tag} 0");
1324     } elsif ($ev->{button} == 2) {
1325     $CONN->send ("apply $item->{tag}");
1326     } elsif ($ev->{button} == 3) {
1327     # examine, lock, mark, maybe other things
1328     warn "MENU not implemented yet\n";
1329     }
1330    
1331     1
1332     },
1333 root 1.173 );
1334    
1335     $hbox->add (new CFClient::UI::Face
1336 root 1.175 can_events => 0,
1337     face => $item->{face},
1338     anim => $item->{anim},
1339     animspeed => $item->{animspeed},
1340 root 1.173 );
1341    
1342     $hbox->add (new CFClient::UI::Label
1343 root 1.175 can_events => 0,
1344     text => $desc,
1345 root 1.173 );
1346     }
1347     });
1348     refresh;
1349     }
1350    
1351 root 1.169 sub conn::container_add {
1352     my ($self, $id, $items) = @_;
1353    
1354 root 1.173 update_floorbox if $id == 0;
1355 root 1.169 # $self-<{player}{tag} => player inv
1356     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1357     }
1358    
1359     sub conn::container_clear {
1360     my ($self, $id) = @_;
1361 root 1.173
1362     update_floorbox if $id == 0;
1363 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1364     }
1365    
1366 root 1.173 sub conn::item_delete {
1367     my ($self, @items) = @_;
1368    
1369     for (@items) {
1370     update_floorbox if $_->{container} == 0;
1371     }
1372     }
1373    
1374     sub conn::item_update {
1375     my ($self, $item) = @_;
1376    
1377     update_floorbox if $item->{container} == 0;
1378     }
1379    
1380 root 1.87 %SDL_CB = (
1381 root 1.145 CFClient::SDL_QUIT => sub {
1382 root 1.87 Event::unloop -1;
1383     },
1384 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1385 root 1.87 },
1386 root 1.153 CFClient::SDL_VIDEOEXPOSE => \&refresh,
1387     CFClient::SDL_ACTIVEEVENT => sub {
1388     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1389 root 1.87 },
1390 root 1.145 CFClient::SDL_KEYDOWN => sub {
1391 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1392 root 1.87 # alt-enter
1393 root 1.134 video_shutdown;
1394 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1395 root 1.134 video_init;
1396 root 1.87 } else {
1397 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1398 elmex 1.23 }
1399 root 1.87 },
1400 root 1.153 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1401     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1402     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1403     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1404     CFClient::SDL_USEREVENT => \&audio_music_finished,
1405 root 1.87 );
1406 elmex 1.23
1407 root 1.1 #############################################################################
1408    
1409 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1410    
1411 root 1.116 $TILECACHE = CFClient::db_table "tilecache";
1412     $FACEMAP = CFClient::db_table "facemap";
1413 root 1.114
1414 root 1.67 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1415 elmex 1.10
1416 root 1.90 my %DEF_CFG = (
1417 root 1.105 sdl_mode => 0,
1418 root 1.90 width => 640,
1419     height => 480,
1420 root 1.105 fullscreen => 0,
1421 root 1.90 fast => 0,
1422 root 1.169 map_scale => 0.5,
1423 root 1.97 fow_enable => 1,
1424 root 1.90 fow_intensity => 0.45,
1425 root 1.92 fow_smooth => 0,
1426 root 1.140 gui_fontsize => 1,
1427 elmex 1.157 log_fontsize => 1,
1428 elmex 1.158 gauge_fontsize => 1,
1429     gauge_size => 0.35,
1430 elmex 1.157 stat_fontsize => 1,
1431 root 1.90 mapsize => 100,
1432     host => "crossfire.schmorp.de",
1433 elmex 1.101 say_command => 'say',
1434 root 1.139 audio_enable => 1,
1435     bgm_enable => 1,
1436 root 1.149 bgm_volume => 0.25,
1437 root 1.90 );
1438    
1439     while (my ($k, $v) = each %DEF_CFG) {
1440     $CFG->{$k} = $v unless exists $CFG->{$k};
1441     }
1442 elmex 1.12
1443 root 1.89 sdl_init;
1444 root 1.87
1445 root 1.93 @SDL_MODES = reverse
1446     grep $_->[0] >= 640 && $_->[1] >= 480,
1447 root 1.145 CFClient::SDL_ListModes;
1448 root 1.87
1449 root 1.89 @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1450    
1451     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1452    
1453 root 1.65 {
1454 root 1.168 my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1455     DejaVuSans.ttf
1456     DejaVuSansMono.ttf
1457     DejaVuSans-Bold.ttf
1458     DejaVuSansMono-Bold.ttf
1459     DejaVuSans-Oblique.ttf
1460     DejaVuSansMono-Oblique.ttf
1461     DejaVuSans-BoldOblique.ttf
1462     DejaVuSansMono-BoldOblique.ttf
1463     );
1464 root 1.65
1465 root 1.67 CFClient::add_font $_ for @fonts;
1466 root 1.168
1467     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1468     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1469    
1470     $FONT_PROP->make_default;
1471 root 1.65 }
1472 root 1.40
1473 root 1.134 video_init;
1474     audio_init;
1475 root 1.122
1476 root 1.87 Event::loop;
1477 root 1.19
1478 root 1.148 END { CFClient::SDL_Quit }
1479 root 1.131
1480 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1481    
1482     Pclient is a Crossfire+ and Crossfire game client.
1483    
1484     =head2 Features
1485    
1486     =over 4
1487    
1488     =item Fullscreen Map
1489    
1490     PClient can uses a fullscreen map, which greatly enhances how much of the
1491     game world you can see.
1492    
1493     =item Persistent Map Cache (Crossfire+ only)
1494    
1495     PClient can persistently cache all map data it received from the
1496     server. This not only allows it to display an overview map, but also
1497     ensures that once-explored areas will be available the next time you want
1498     to explore more.
1499    
1500     =item Hardware acceleration
1501    
1502     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1503     acceleration. Most modern graphics cards have difficulties with 2D
1504     acceleration, while 3D graphics is accelerated well.
1505    
1506     =item No arbitrary limits
1507    
1508     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1509     limits (like a fixed amount of face numbers). There are still limits, but
1510     they are not arbitrarily low :)
1511    
1512     =back
1513    
1514 root 1.179 =head1 USAGE
1515    
1516     =head2 The Map
1517    
1518     The map is always displayed in the background, behind all other windows and UI elements.
1519    
1520     #TODO# middle-click scrolls
1521     #
1522     # keys:
1523     #
1524     # a apply
1525     # keypad moves, kp_5 applies ranged attack to self
1526    
1527     Starting to type enters the I<completion mode>. In that mode, you can type
1528     abbreviations or commands and have them executed as soon as they match a
1529     valid command. This is best explained by a few examples:
1530    
1531     Typing B<climb> will display a list of commands with I<climb> in their
1532     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1533    
1534     You can abbreviate commands by typing only the first character of every
1535     word. For example, typing I<iwor> will likely select I<invoke word of
1536     recall>, while I<ccfo> will select I<cast create food>. Likewise, I<rscli>
1537     will likely select I<ready_skill climbing> and I<usl> will give you
1538     I<use_skill levitation>.
1539    
1540     =head2 The map overview
1541    
1542     #TODO#
1543    
1544     =head2 The Status area in the lower right corner
1545    
1546     #TODO#
1547    
1548     =head2 The I<Statistics>/I>Stats> window
1549    
1550     #TODO#
1551    
1552 root 1.178 =head1 FAQ
1553    
1554     =over 4
1555    
1556     =item The client is very sluggish and slow, what can I do about this?
1557    
1558     Most likely, you don't have accelerated OpenGL support. Try to find a
1559     newer driver, or a driver from your hardware vendor, that features OpenGL
1560     support.
1561    
1562     If this is not an option, the following Setup options reduce the load and
1563     will likely make the client playable with sofwtare rendering (it will
1564     still be slow, though):
1565    
1566     =over 4
1567    
1568     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1569    
1570     =item Enable B<Fast & Ugly> mode
1571    
1572     =item Disable B<Fog of War>
1573    
1574     =item Increase B<Map Scale>
1575    
1576     =back
1577    
1578     =back
1579    
1580     =head1 AUTHOR
1581    
1582     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1583    
1584    
1585 root 1.82