ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.185
Committed: Tue Apr 25 11:48:31 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.184: +2 -1 lines
Log Message:
require alpha channel again after fixing premulitiplied alpha value - sofwtare rendering die die die

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