ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.199
Committed: Mon May 8 15:12:39 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.198: +16 -4 lines
Log Message:
*** empty log message ***

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