ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.182
Committed: Tue Apr 25 10:40:49 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.181: +1 -1 lines
Log Message:
doh

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 elmex 1.156 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     can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
416     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
417     can_hover => 1, can_events => 1, fg => $black, valign => 0, align => -1, text => $label, tooltip => $tooltip);
418     }
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.180 template => "-100%",
453     align => +1,
454     valign => 0,
455     can_events => 1,
456     can_hover => 1,
457     tooltip => $resist_names{$_},
458 root 1.168 );
459     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
460 root 1.180 can_hover => 1,
461 root 1.168 can_events => 1,
462 root 1.180 image => "ui/resist/resist_$_.png",
463     tooltip => $resist_names{$_},
464 elmex 1.156 );
465    
466     $row++;
467     if ($row % 6 == 0) {
468     $col += 2;
469     $row = 0;
470     }
471     }
472    
473 elmex 1.157 &set_stats_window_fontsize;
474 elmex 1.156 update_stats_window ({});
475 root 1.155
476 elmex 1.154 $tgw
477     }
478    
479 root 1.169 sub formsep {
480     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
481     }
482    
483 elmex 1.154 sub update_stats_window {
484     my ($stats) = @_;
485    
486 elmex 1.156 # i love text protocols!!!
487 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
488     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
489     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
490     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
491     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
492 elmex 1.156 my $fo_m = 999;
493 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
494     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
495 elmex 1.156
496     $GAUGES->{hp} ->set_value ($hp, $hp_m);
497     $GAUGES->{mana} ->set_value ($sp, $sp_m);
498     $GAUGES->{food} ->set_value ($fo, $fo_m);
499     $GAUGES->{grace} ->set_value ($gr, $gr_m);
500 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
501     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
502     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
503 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
504     $GAUGES->{range} ->set_text ("Rng: " . $rng);
505 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
506 elmex 1.165 $title =~ s/^Player: //;
507     $STATWIDS->{title} ->set_text ("Title: " . $title);
508 elmex 1.156
509 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
510     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
511     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
512     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
513     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
514     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
515     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
516     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
517     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
518     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
519     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
520     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
521     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
522 elmex 1.156
523     my %tbl = (
524     phys => 100,
525     magic => 101,
526     fire => 102,
527     elec => 103,
528     cold => 104,
529     conf => 105,
530     acid => 106,
531     drain => 107,
532     ghit => 108,
533     pois => 109,
534     slow => 110,
535     para => 111,
536     tund => 112,
537     fear => 113,
538 elmex 1.165 depl => 113,
539 elmex 1.156 deat => 115,
540     holyw => 116,
541     blind => 117
542 elmex 1.154 );
543 elmex 1.156
544     for (keys %tbl) {
545     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
546     }
547    
548 elmex 1.154 }
549    
550 root 1.112 sub metaserver_dialog {
551     my $dialog = new CFClient::UI::FancyFrame
552 root 1.150 title => "Metaserver",
553 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
554    
555     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
556    
557     $dialog
558     }
559    
560 root 1.179 my $METASERVER_ATIME;
561    
562 root 1.112 sub update_metaserver {
563 root 1.114 my ($HOST) = @_;
564    
565 root 1.179 return if $METASERVER_ATIME > time;
566     $METASERVER_ATIME = time + 60;
567    
568 root 1.178 my $table = $METASERVER->{table};
569     $table->clear;
570 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
571 root 1.112
572     my $buf;
573    
574     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
575    
576 root 1.178 unless ($fh) {
577     $label->set_text ("unable to contact metaserver: $!");
578     return;
579     }
580    
581 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
582     my $res = sysread $fh, $buf, 8192, length $buf;
583    
584     if (!defined $res) {
585     $_[0]->w->cancel;
586 root 1.178 $label->set_text ("error while retrieving server list: $!");
587 root 1.112 } elsif ($res == 0) {
588     $_[0]->w->cancel;
589     status "server list retrieved";
590 root 1.113
591 root 1.178 utf8::decode $buf if utf8::valid $buf;
592 root 1.113
593     $table->clear;
594    
595 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
596 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
597     for 0 .. $#col;
598    
599     my @align = qw(1 0 1 1 -1);
600    
601     my $y = 0;
602 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
603 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
604    
605     for ($desc) {
606     s/<br>/\n/gi;
607     s/<li>/\n· /gi;
608     s/<.*?>//sgi;
609     s/&/&amp;/g;
610     s/</&lt;/g;
611     s/>/&gt;/g;
612     }
613    
614     $uptime = sprintf "%dd %02d:%02d:%02d",
615     (int $m->[8] / 86400),
616     (int $m->[8] / 3600) % 24,
617     (int $m->[8] / 60) % 60,
618     $m->[8] % 60;
619    
620     $m = [$users, $host, $uptime, $version, $desc];
621    
622     $y++;
623 root 1.114
624     $table->add (0, $y, new CFClient::UI::VBox children => [
625 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
626 root 1.114 $HOST->set_text ($CFG->{host} = $host);
627     }),
628     (new CFClient::UI::Empty expand => 1),
629     ]);
630    
631 root 1.140 $table->add ($_ + 1, $y, new CFClient::UI::Label align => $align[$_], text => $m->[$_], fontsize => 0.8)
632 root 1.113 for 0 .. $#$m;
633     }
634 root 1.112 }
635     });
636     }
637    
638 root 1.111 sub server_setup {
639     my $dialog = new CFClient::UI::FancyFrame
640 root 1.150 title => "Server Setup",
641 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
642 root 1.81
643 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
644 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
645 root 1.112
646     {
647     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
648    
649 elmex 1.166 $vbox->add (
650     my $HOST = new CFClient::UI::Entry
651     expand => 1,
652     text => $CFG->{host},
653     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
654     connect_changed => sub {
655     my ($self, $value) = @_;
656     $CFG->{host} = $value;
657     }
658     );
659 root 1.112
660     $METASERVER = metaserver_dialog;
661 elmex 1.101
662 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
663     expand => 1,
664     text => "Metaserver",
665     other => $METASERVER,
666 root 1.167 tooltip => "Show a list of avaible crossfire servers",
667 elmex 1.166 connect_open => sub {
668     update_metaserver $HOST;
669     }
670     );
671 root 1.112 }
672 root 1.81
673 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
674 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
675     text => $CFG->{user},
676     tooltip => "The name of your character on the server",
677     connect_changed => sub {
678     my ($self, $value) = @_;
679     $CFG->{user} = $value;
680     }
681     );
682 root 1.81
683 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
684 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
685     text => $CFG->{password},
686     hidden => 1,
687     tooltip => "The password for your character",
688     connect_changed => sub {
689     my ($self, $value) = @_;
690     $CFG->{password} = $value;
691     }
692     );
693 elmex 1.101
694 root 1.141 $table->add (0, 6, new CFClient::UI::Label valign => 0, align => 1, text => "Def. say cmd");
695 elmex 1.166 $table->add (1, 6, my $saycmd = new CFClient::UI::Entry
696     text => $CFG->{say_command},
697     tooltip => "This is the command that will be used if you write a line in the message window entry. "
698     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
699 root 1.173 ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
700 elmex 1.166 connect_changed => sub {
701     my ($self, $value) = @_;
702     $CFG->{say_command} = $value;
703     }
704     );
705 root 1.81
706 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
707 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
708 root 1.81 req_w => 100,
709     range => [$CFG->{mapsize}, 10, 100 + 1, 1],
710 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
711     ."If you set this to a high value you will be able to see further for example.",
712 root 1.81 connect_changed => sub {
713     my ($self, $value) = @_;
714    
715     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
716     },
717     );
718    
719 elmex 1.101 $table->add (1, 8, new CFClient::UI::Button expand => 1, align => 0, text => "Login", connect_activate => sub {
720 root 1.84 start_game;
721 root 1.82 });
722    
723 root 1.98 $dialog
724 root 1.81 }
725 root 1.58
726 root 1.111 sub message_window {
727 root 1.99 my $window = new CFClient::UI::FancyFrame
728 root 1.150 title => "Messages",
729 root 1.99 border_bg => [1, 1, 1, 0.5],
730     bg => [0.3, 0.3, 0.3, 0.8],
731 root 1.124 user_w => int $::WIDTH / 3,
732     user_h => int $::HEIGHT / 5,
733 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
734    
735 root 1.105 $vbox->add ($LOGVIEW = new CFClient::UI::TextView
736     expand => 1,
737 root 1.168 font => $FONT_FIXED,
738 root 1.105 fontsize => $::CFG->{log_fontsize},
739     );
740    
741 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
742 elmex 1.118 connect_focus_in => sub {
743     my ($input, $prev_focus) = @_;
744    
745     delete $input->{refocus_map};
746    
747     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
748     $input->{refocus_map} = 1;
749     }
750     delete $input->{auto_activated};
751     },
752 root 1.116 connect_activate => sub {
753 elmex 1.100 my ($input, $text) = @_;
754     $input->set_text ('');
755    
756     if ($text =~ /^\/(.*)/) {
757 root 1.123 $::CONN->user_send ($1);
758 elmex 1.100 } else {
759 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
760 root 1.123 $::CONN->user_send ("$say_cmd $text");
761 elmex 1.100 }
762 elmex 1.118 if ($input->{refocus_map}) {
763     delete $input->{refocus_map};
764     $MAPWIDGET->focus_in
765     }
766 root 1.116 },
767     connect_escape => sub {
768 elmex 1.102 $MAPWIDGET->focus_in
769 root 1.116 },
770     );
771 elmex 1.102
772     $CONSOLE = {
773     window => $window,
774     input => $input
775     };
776 root 1.99
777     $window
778     }
779    
780 root 1.89 sub sdl_init {
781 root 1.145 CFClient::SDL_Init
782 root 1.89 and die "SDL::Init failed!\n";
783     }
784    
785 root 1.134 sub video_init {
786 root 1.89 sdl_init;
787    
788 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
789     $FULLSCREEN = $CFG->{fullscreen};
790 root 1.89 $FAST = $CFG->{fast};
791 root 1.84
792 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
793     or die "SDL_SetVideoMode failed!\n";
794 root 1.2
795 root 1.86 $SDL_ACTIVE = 1;
796    
797 root 1.87 $LAST_REFRESH = time - 0.01;
798 root 1.45
799 root 1.67 CFClient::gl_init;
800 root 1.30
801 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
802 root 1.39
803 root 1.52 #############################################################################
804    
805 root 1.99 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100;
806 root 1.140 $DEBUG_STATUS->show;
807 root 1.52
808 root 1.72 $STATUS_LINE = new CFClient::UI::Label
809 root 1.77 padding => 0,
810 root 1.140 y => $HEIGHT - $FONTSIZE * 1.8;
811     $STATUS_LINE->show;
812 root 1.51
813 root 1.72 $ALT_ENTER_MESSAGE = new CFClient::UI::Label
814 root 1.123 padding => 0,
815 root 1.140 fontsize => 0.8,
816 root 1.123 markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode";
817 root 1.140 $ALT_ENTER_MESSAGE->show;
818     $ALT_ENTER_MESSAGE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h});
819 root 1.30
820 root 1.141 $CFClient::UI::ROOT->add ($MAPWIDGET = new CFClient::MapWidget);
821 root 1.69 $MAPWIDGET->focus_in;
822 elmex 1.102 $MAPWIDGET->connect (activate_console => sub {
823 elmex 1.103 my ($mapwidget, $preset) = @_;
824    
825 elmex 1.102 if ($CONSOLE) {
826 elmex 1.118 $CONSOLE->{input}->{auto_activated} = 1;
827 elmex 1.102 $CONSOLE->{input}->focus_in;
828 elmex 1.103
829     if ($preset && $CONSOLE->{input}->get_text eq '') {
830     $CONSOLE->{input}->set_text ($preset);
831     }
832 elmex 1.102 }
833     });
834 root 1.81
835 root 1.111 $CFClient::UI::ROOT->add ($BUTTONBAR = new CFClient::UI::HBox);
836    
837     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup);
838     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup);
839     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window);
840    
841 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
842     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window);
843    
844 root 1.111 $BUTTONBAR->add (new CFClient::UI::Button text => "Save Config", connect_activate => sub {
845     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
846     status "Configuration Saved";
847     });
848 root 1.98
849 root 1.119 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
850 root 1.2 }
851    
852 root 1.134 sub video_shutdown {
853 root 1.111 $CFClient::UI::ROOT->{children} = [];
854 root 1.177 undef $CFClient::UI::GRAB;
855     undef $CFClient::UI::HOVER;
856 root 1.86 undef $SDL_ACTIVE;
857 root 1.134 }
858    
859 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#
860 root 1.135 my $bgmusic;#TODO#hack#d#
861    
862 root 1.153 sub audio_music_finished {
863     return unless $CFG->{bgm_enable};
864    
865     # TODO: hack, do play loop and mood music
866     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
867     $bgmusic->play (0);
868    
869     push @bgmusic, shift @bgmusic;
870     }
871    
872 root 1.134 sub audio_init {
873 root 1.139 if ($CFG->{audio_enable}) {
874 root 1.134 if (open my $fh, "<:utf8", CFClient::find_rcfile "sounds/config") {
875 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
876     CFClient::Mix_AllocateChannels 8;
877 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
878 root 1.134
879 root 1.153 audio_music_finished;
880 root 1.135
881 root 1.134 while (<$fh>) {
882     next if /^\s*#/;
883     next if /^\s*$/;
884    
885     my ($file, $volume, $event) = split /\s+/, $_, 3;
886    
887     push @SOUNDS, "$volume,$file";
888    
889     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
890 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
891 root 1.134 $chunk->volume ($volume * 128 / 100);
892     $chunk
893     };
894     }
895     } else {
896     status "unable to open sound config: $!";
897     }
898     }
899     }
900    
901     sub audio_shutdown {
902 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
903 root 1.134 undef $SDL_MIXER;
904     @SOUNDS = ();
905     %AUDIO_CHUNKS = ();
906 root 1.62 }
907    
908 root 1.87 my %animate_object;
909     my $animate_timer;
910    
911     my $want_refresh;
912     my $can_refresh;
913    
914     my $fps = 9;
915    
916 root 1.30 sub force_refresh {
917 root 1.87 $fps = $fps * 0.95 + 1 / ($NOW - $LAST_REFRESH) * 0.05;
918     debug sprintf "%3.2f", $fps;
919    
920 root 1.96 $want_refresh = 0;
921 root 1.87 $can_refresh = 0;
922    
923 root 1.111 $CFClient::UI::ROOT->draw;
924 root 1.1
925 root 1.148 CFClient::SDL_GL_SwapBuffers;
926 root 1.87
927     $LAST_REFRESH = $NOW;
928 root 1.1 }
929    
930 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
931     $NOW = time;
932    
933 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
934     for CFClient::SDL_PollEvent;
935 root 1.87
936     if (%animate_object) {
937     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
938     $want_refresh++;
939     }
940    
941     if ($want_refresh) {
942     force_refresh;
943     } else {
944     $can_refresh = 1;
945     }
946     });
947 root 1.64
948 root 1.30 sub refresh {
949 root 1.87 $want_refresh++;
950 root 1.30 }
951    
952 root 1.45 sub animation_start {
953     my ($widget) = @_;
954 root 1.87 $animate_object{$widget} = $widget;
955 root 1.45 }
956    
957     sub animation_stop {
958     my ($widget) = @_;
959 root 1.87 delete $animate_object{$widget};
960 root 1.45 }
961    
962 root 1.2 @conn::ISA = Crossfire::Protocol::;
963 root 1.1
964 elmex 1.125 sub conn::stats_update {
965     my ($self, $stats) = @_;
966    
967 elmex 1.154 update_stats_window ($stats);
968 elmex 1.125 }
969    
970 root 1.89 sub conn::user_send {
971 root 1.88 my ($self, $command) = @_;
972    
973 root 1.123 $self->send_command ($command);
974 root 1.88 status $command;
975     }
976    
977 root 1.119 sub conn::map_scroll {
978     my ($self, $dx, $dy) = @_;
979    
980     $MAP->scroll ($dx, $dy);
981     }
982    
983 root 1.94 sub conn::feed_map1a {
984     my ($self, $data) = @_;
985    
986 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
987 root 1.1
988 root 1.95 $MAP->map1a_update ($data);
989 root 1.69 $MAPWIDGET->update;
990 root 1.1 }
991    
992 root 1.116 sub conn::flush_map {
993     my ($self) = @_;
994    
995     my $map_info = delete $self->{map_info}
996     or return;
997    
998     my ($hash, $x, $y, $w, $h) = @$map_info;
999    
1000     my $data = $MAP->get_rect ($x, $y, $w, $h);
1001     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1002 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1003 root 1.116 }
1004 root 1.1
1005 root 1.2 sub conn::map_clear {
1006 root 1.1 my ($self) = @_;
1007    
1008 root 1.116 $self->flush_map;
1009 root 1.150 delete $self->{neigh_map};
1010 root 1.116
1011 root 1.95 $MAP->clear;
1012 root 1.1 }
1013    
1014 root 1.116
1015 root 1.119 sub conn::load_map($$$) {
1016     my ($self, $hash, $x, $y) = @_;
1017 root 1.115
1018 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1019     $data = Compress::LZF::decompress $data;
1020 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1021 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1022     my $data = $TILECACHE->get ($id)
1023     or next;
1024    
1025     $self->set_texture ($id => $data);
1026     }
1027     }
1028 root 1.115 }
1029    
1030 root 1.152 # this method does a "flood fill" into every tile direction
1031     # it assumes that tiles are arranged in a rectangular grid,
1032     # i.e. a map is the same as the left of the right map etc.
1033     # failure to comply are harmless and result in display errors
1034     # at worst.
1035 root 1.119 sub conn::flood_fill {
1036 root 1.150 my ($self, $gx, $gy, $path, $hash, $flags) = @_;
1037 root 1.119
1038 root 1.121 # the server does not allow map paths > 6
1039 root 1.120 return if 6 <= length $path;
1040    
1041 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1042    
1043     for (
1044     [1, 0, -1],
1045     [2, 1, 0],
1046     [3, 0, 1],
1047     [4, -1, 0],
1048     ) {
1049     my ($tile, $dx, $dy) = @$_;
1050    
1051     my $gx = $gx + $dx;
1052     my $gy = $gy + $dy;
1053    
1054 root 1.119 next unless $flags & (1 << ($tile - 1));
1055 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1056 root 1.119
1057 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1058     if (my $info = $neigh->[$tile]) {
1059     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1060 root 1.119
1061 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1062     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1063    
1064     } else {
1065     $self->send_mapinfo ("spatial $path$tile", sub {
1066     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1067 root 1.119
1068 root 1.150 return if $mode ne "spatial";
1069 root 1.119
1070 root 1.150 $x += $MAP->ox;
1071     $y += $MAP->oy;
1072    
1073     $self->load_map ($hash, $x, $y)
1074     unless $self->{neigh_map}{$hash}[5]++;#d#
1075 root 1.119
1076 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1077 root 1.119
1078 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1079     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1080     });
1081     }
1082 root 1.119 }
1083     }
1084    
1085     sub conn::map_change {
1086     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1087    
1088     $self->flush_map;
1089    
1090     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1091    
1092     my $mapmapw = 250;
1093     my $mapmaph = 250;
1094 root 1.150
1095     $self->{neigh_rect} = [
1096 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1097     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1098 root 1.150 ];
1099 root 1.119
1100 root 1.150 delete $self->{neigh_grid};
1101     $self->flood_fill (0, 0, "", $hash, $flags);
1102 root 1.119
1103     $x += $ox;
1104     $y += $oy;
1105    
1106     $self->{map_info} = [$hash, $x, $y, $w, $h];
1107    
1108 elmex 1.158 my $map = $self->{map_info}[0];
1109     $map =~ s/^.*?\/([^\/]+)$/\1/;
1110     $STATWIDS->{map}->set_text ("Map: " . $map);
1111 elmex 1.157
1112 root 1.119 $self->load_map ($hash, $x, $y);
1113     }
1114    
1115 root 1.19 sub conn::face_find {
1116 root 1.116 my ($self, $facenum, $face) = @_;
1117    
1118     my $hash = "$face->{chksum},$face->{name}";
1119    
1120     my $id = $FACEMAP->get ($hash);
1121    
1122     unless ($id) {
1123     # create new id for face
1124     # i love transactions
1125     for (1..100) {
1126     my $txn = $CFClient::DB_ENV->txn_begin;
1127     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1128     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1129     $id++;
1130     if ($FACEMAP->put (id => $id) == 0
1131     && $FACEMAP->put ($hash => $id) == 0) {
1132     $txn->txn_commit;
1133    
1134     goto gotid;
1135     }
1136     }
1137     $txn->abort;
1138     }
1139 root 1.19
1140 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1141     }
1142 root 1.114
1143 root 1.116 gotid:
1144     $face->{id} = $id;
1145     $MAP->set_face ($facenum => $id);
1146 root 1.173 $self->{faceid}[$facenum] = $id;#d#
1147 root 1.116 $TILECACHE->get ($id)
1148 root 1.19 }
1149    
1150 root 1.2 sub conn::face_update {
1151 root 1.95 my ($self, $facenum, $face) = @_;
1152 root 1.19
1153 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1154    
1155     $self->set_texture ($face->{id} => delete $face->{image});
1156     }
1157 root 1.1
1158 root 1.116 sub conn::set_texture {
1159     my ($self, $id, $data) = @_;
1160 root 1.95
1161 root 1.116 $self->{texture}[$id] ||= do {
1162     my $tex =
1163     new_from_image CFClient::Texture
1164 root 1.173 $data, minify => 1, mipmap => 1;
1165 root 1.116
1166     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1167     $MAPWIDGET->update;
1168    
1169     $tex
1170     };
1171 root 1.1 }
1172    
1173 root 1.134 sub conn::sound_play {
1174     my ($self, $x, $y, $soundnum, $type) = @_;
1175    
1176 root 1.139 $SDL_MIXER
1177     or return;
1178    
1179 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1180     or return;
1181    
1182 root 1.146 $chunk->play;
1183 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1184 root 1.134 }
1185    
1186 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1187    
1188 root 1.33 sub conn::query {
1189     my ($self, $flags, $prompt) = @_;
1190    
1191 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1192     $LAST_QUERY = $prompt;
1193    
1194     my $dialog = new CFClient::UI::FancyFrame
1195     title => "Query",
1196     child => my $vbox = new CFClient::UI::VBox;
1197    
1198     $vbox->add (new CFClient::UI::Label
1199     max_w => $::WIDTH * 0.4,
1200     text => $prompt);
1201    
1202     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1203     $vbox->add (my $hbox = new CFClient::HBox);
1204     $hbox->add (new CFClient::Button
1205     text => "No",
1206     connect_activate => sub {
1207     $self->send ("reply n");
1208     $dialog->destroy;
1209     $MAPWIDGET->focus_in;
1210     }
1211     );
1212     $hbox->add (new CFClient::Button
1213     text => "Yes",
1214     connect_activate => sub {
1215     $self->send ("reply y");
1216     $dialog->destroy;
1217     $MAPWIDGET->focus_in;
1218     },
1219     );
1220    
1221     $dialog->focus_in;
1222    
1223     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1224     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1225     $vbox->add (my $entry = new CFClient::UI::Entry
1226     connect_changed => sub {
1227     $self->send ("reply $_[1]");
1228     $dialog->destroy;
1229     $MAPWIDGET->focus_in;
1230     },
1231     );
1232    
1233     $entry->focus_in;
1234    
1235     } else {
1236     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1237    
1238     $vbox->add (my $entry = new CFClient::UI::Entry
1239     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1240     connect_activate => sub {
1241     $self->send ("reply $_[1]");
1242     $dialog->destroy;
1243     $MAPWIDGET->focus_in;
1244     },
1245     );
1246    
1247     $entry->focus_in;
1248     }
1249    
1250     $dialog->show;
1251 root 1.33 }
1252    
1253 root 1.99 sub conn::drawinfo {
1254     my ($self, $color, $text) = @_;
1255    
1256     my @color = (
1257     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1258     [1.00, 1.00, 1.00],
1259 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1260 root 1.99 [1.00, 0.00, 0.00],
1261     [1.00, 0.54, 0.00],
1262     [0.11, 0.56, 1.00],
1263     [0.93, 0.46, 0.00],
1264     [0.18, 0.54, 0.34],
1265     [0.56, 0.73, 0.56],
1266     [0.80, 0.80, 0.80],
1267     [0.55, 0.41, 0.13],
1268     [0.99, 0.77, 0.26],
1269     [0.74, 0.65, 0.41],
1270     );
1271    
1272     $LOGVIEW->add_paragraph ($color[$color], $text);
1273     }
1274    
1275 root 1.144 sub conn::spell_add {
1276 root 1.143 my ($self, $spell) = @_;
1277    
1278 root 1.171 # TODO
1279     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1280     $MAPWIDGET->add_command ("invoke $spell->{name}", $spell->{message});
1281     $MAPWIDGET->add_command ("cast $spell->{name}", $spell->{message});
1282 root 1.144 }
1283    
1284     sub conn::spell_delete {
1285     my ($self, $spell) = @_;
1286     }
1287    
1288     sub conn::addme_success {
1289     my ($self) = @_;
1290    
1291     for my $skill (values %{$self->{skill_info}}) {
1292 root 1.171 $MAPWIDGET->add_command ("ready_skill $skill", "Ready the skill '$skill'");
1293     $MAPWIDGET->add_command ("use_skill $skill", "Immediately use the skill '$skill'");
1294 root 1.144 }
1295 root 1.143 }
1296    
1297 root 1.173 sub update_floorbox {
1298     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1299     $FLOORBOX->clear;
1300     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1301    
1302     my @items = values %{ $CONN->{container}{0} };
1303    
1304     # we basically have to use the same sorting as everybody else
1305     @items = sort { $a->{type} <=> $b->{type} } @items;
1306    
1307     for my $item (reverse @items) {
1308     my $desc = $item->{nrof} < 2
1309     ? $item->{name}
1310     : "$item->{nrof} $item->{name_pl}";
1311     # todo: animation widget, face widget, weight(?) etc.
1312     $FLOORBOX->add (my $hbox = new CFClient::UI::HBox
1313     tooltip => (CFClient::UI::Label->escape ($desc)
1314     . "\n<small>leftclick - pick up\nmiddle click - apply\nrightclick - menu</small>"),
1315     can_hover => 1,
1316     can_events => 1,
1317 root 1.174 connect_button_down => sub {
1318     my ($self, $ev, $x, $y) = @_;
1319    
1320     # todo: maybe put examine on 1? but should just be a tooltip :(
1321     if ($ev->{button} == 1) {
1322     $CONN->send ("move $CONN->{player}{tag} $item->{tag} 0");
1323     } elsif ($ev->{button} == 2) {
1324     $CONN->send ("apply $item->{tag}");
1325     } elsif ($ev->{button} == 3) {
1326     # examine, lock, mark, maybe other things
1327     warn "MENU not implemented yet\n";
1328     }
1329    
1330     1
1331     },
1332 root 1.173 );
1333    
1334     $hbox->add (new CFClient::UI::Face
1335 root 1.175 can_events => 0,
1336     face => $item->{face},
1337     anim => $item->{anim},
1338     animspeed => $item->{animspeed},
1339 root 1.173 );
1340    
1341     $hbox->add (new CFClient::UI::Label
1342 root 1.175 can_events => 0,
1343     text => $desc,
1344 root 1.173 );
1345     }
1346     });
1347     refresh;
1348     }
1349    
1350 root 1.169 sub conn::container_add {
1351     my ($self, $id, $items) = @_;
1352    
1353 root 1.173 update_floorbox if $id == 0;
1354 root 1.169 # $self-<{player}{tag} => player inv
1355     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1356     }
1357    
1358     sub conn::container_clear {
1359     my ($self, $id) = @_;
1360 root 1.173
1361     update_floorbox if $id == 0;
1362 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1363     }
1364    
1365 root 1.173 sub conn::item_delete {
1366     my ($self, @items) = @_;
1367    
1368     for (@items) {
1369     update_floorbox if $_->{container} == 0;
1370     }
1371     }
1372    
1373     sub conn::item_update {
1374     my ($self, $item) = @_;
1375    
1376     update_floorbox if $item->{container} == 0;
1377     }
1378    
1379 root 1.87 %SDL_CB = (
1380 root 1.145 CFClient::SDL_QUIT => sub {
1381 root 1.87 Event::unloop -1;
1382     },
1383 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1384 root 1.87 },
1385 root 1.153 CFClient::SDL_VIDEOEXPOSE => \&refresh,
1386     CFClient::SDL_ACTIVEEVENT => sub {
1387     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1388 root 1.87 },
1389 root 1.145 CFClient::SDL_KEYDOWN => sub {
1390 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1391 root 1.87 # alt-enter
1392 root 1.134 video_shutdown;
1393 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1394 root 1.134 video_init;
1395 root 1.87 } else {
1396 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1397 elmex 1.23 }
1398 root 1.87 },
1399 root 1.153 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1400     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1401     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1402     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1403     CFClient::SDL_USEREVENT => \&audio_music_finished,
1404 root 1.87 );
1405 elmex 1.23
1406 root 1.1 #############################################################################
1407    
1408 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1409    
1410 root 1.116 $TILECACHE = CFClient::db_table "tilecache";
1411     $FACEMAP = CFClient::db_table "facemap";
1412 root 1.114
1413 root 1.67 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1414 elmex 1.10
1415 root 1.90 my %DEF_CFG = (
1416 root 1.105 sdl_mode => 0,
1417 root 1.90 width => 640,
1418     height => 480,
1419 root 1.105 fullscreen => 0,
1420 root 1.90 fast => 0,
1421 root 1.169 map_scale => 0.5,
1422 root 1.97 fow_enable => 1,
1423 root 1.90 fow_intensity => 0.45,
1424 root 1.92 fow_smooth => 0,
1425 root 1.140 gui_fontsize => 1,
1426 elmex 1.157 log_fontsize => 1,
1427 elmex 1.158 gauge_fontsize => 1,
1428     gauge_size => 0.35,
1429 elmex 1.157 stat_fontsize => 1,
1430 root 1.90 mapsize => 100,
1431     host => "crossfire.schmorp.de",
1432 elmex 1.101 say_command => 'say',
1433 root 1.139 audio_enable => 1,
1434     bgm_enable => 1,
1435 root 1.149 bgm_volume => 0.25,
1436 root 1.90 );
1437    
1438     while (my ($k, $v) = each %DEF_CFG) {
1439     $CFG->{$k} = $v unless exists $CFG->{$k};
1440     }
1441 elmex 1.12
1442 root 1.89 sdl_init;
1443 root 1.87
1444 root 1.93 @SDL_MODES = reverse
1445     grep $_->[0] >= 640 && $_->[1] >= 480,
1446 root 1.145 CFClient::SDL_ListModes;
1447 root 1.87
1448 root 1.89 @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1449    
1450     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1451    
1452 root 1.65 {
1453 root 1.168 my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1454     DejaVuSans.ttf
1455     DejaVuSansMono.ttf
1456     DejaVuSans-Bold.ttf
1457     DejaVuSansMono-Bold.ttf
1458     DejaVuSans-Oblique.ttf
1459     DejaVuSansMono-Oblique.ttf
1460     DejaVuSans-BoldOblique.ttf
1461     DejaVuSansMono-BoldOblique.ttf
1462     );
1463 root 1.65
1464 root 1.67 CFClient::add_font $_ for @fonts;
1465 root 1.168
1466     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1467     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1468    
1469     $FONT_PROP->make_default;
1470 root 1.65 }
1471 root 1.40
1472 root 1.134 video_init;
1473     audio_init;
1474 root 1.122
1475 root 1.87 Event::loop;
1476 root 1.19
1477 root 1.148 END { CFClient::SDL_Quit }
1478 root 1.131
1479 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1480    
1481     Pclient is a Crossfire+ and Crossfire game client.
1482    
1483     =head2 Features
1484    
1485     =over 4
1486    
1487     =item Fullscreen Map
1488    
1489     PClient can uses a fullscreen map, which greatly enhances how much of the
1490     game world you can see.
1491    
1492     =item Persistent Map Cache (Crossfire+ only)
1493    
1494     PClient can persistently cache all map data it received from the
1495     server. This not only allows it to display an overview map, but also
1496     ensures that once-explored areas will be available the next time you want
1497     to explore more.
1498    
1499     =item Hardware acceleration
1500    
1501     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1502     acceleration. Most modern graphics cards have difficulties with 2D
1503     acceleration, while 3D graphics is accelerated well.
1504    
1505     =item No arbitrary limits
1506    
1507     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1508     limits (like a fixed amount of face numbers). There are still limits, but
1509     they are not arbitrarily low :)
1510    
1511     =back
1512    
1513 root 1.179 =head1 USAGE
1514    
1515     =head2 The Map
1516    
1517     The map is always displayed in the background, behind all other windows and UI elements.
1518    
1519     #TODO# middle-click scrolls
1520     #
1521     # keys:
1522     #
1523     # a apply
1524     # keypad moves, kp_5 applies ranged attack to self
1525    
1526     Starting to type enters the I<completion mode>. In that mode, you can type
1527     abbreviations or commands and have them executed as soon as they match a
1528     valid command. This is best explained by a few examples:
1529    
1530     Typing B<climb> will display a list of commands with I<climb> in their
1531     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1532    
1533     You can abbreviate commands by typing only the first character of every
1534     word. For example, typing I<iwor> will likely select I<invoke word of
1535     recall>, while I<ccfo> will select I<cast create food>. Likewise, I<rscli>
1536     will likely select I<ready_skill climbing> and I<usl> will give you
1537     I<use_skill levitation>.
1538    
1539     =head2 The map overview
1540    
1541     #TODO#
1542    
1543     =head2 The Status area in the lower right corner
1544    
1545     #TODO#
1546    
1547     =head2 The I<Statistics>/I>Stats> window
1548    
1549     #TODO#
1550    
1551 root 1.178 =head1 FAQ
1552    
1553     =over 4
1554    
1555     =item The client is very sluggish and slow, what can I do about this?
1556    
1557     Most likely, you don't have accelerated OpenGL support. Try to find a
1558     newer driver, or a driver from your hardware vendor, that features OpenGL
1559     support.
1560    
1561     If this is not an option, the following Setup options reduce the load and
1562     will likely make the client playable with sofwtare rendering (it will
1563     still be slow, though):
1564    
1565     =over 4
1566    
1567     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1568    
1569     =item Enable B<Fast & Ugly> mode
1570    
1571     =item Disable B<Fog of War>
1572    
1573     =item Increase B<Map Scale>
1574    
1575     =back
1576    
1577     =back
1578    
1579     =head1 AUTHOR
1580    
1581     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1582    
1583    
1584 root 1.82