ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.223
Committed: Wed May 17 14:55:15 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.222: +25 -2 lines
Log Message:
minor fixes on the inventory

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