ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.188
Committed: Tue Apr 25 13:27:00 2006 UTC (18 years, 1 month ago) by elmex
Branch: MAIN
Changes since 1.187: +12 -12 lines
Log Message:
moved the say command to client setup

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