ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/MapWidget.pm
Revision: 1.163
Committed: Thu Oct 14 00:02:39 2010 UTC (13 years, 8 months ago) by root
Branch: MAIN
Changes since 1.162: +3 -4 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.126 package DC::MapWidget;
2 root 1.1
3 root 1.162 use common::sense;
4 root 1.1
5     use List::Util qw(min max);
6    
7 root 1.126 use DC;
8     use DC::OpenGL;
9     use DC::UI;
10     use DC::Macro;
11 root 1.1
12 root 1.126 our @ISA = DC::UI::Base::;
13 root 1.1
14 root 1.159 our @TEX_HIDDEN = map {
15     new_from_resource DC::Texture # MUST be POT
16     "hidden-$_.png", mipmap => 1, wrap => 1
17     } 0, 1, 2;
18    
19 root 1.61 my $magicmap_tex =
20 root 1.143 new_from_resource DC::Texture "magicmap.png",
21 root 1.61 mipmap => 1, wrap => 0, internalformat => GL_ALPHA;
22    
23 root 1.1 sub new {
24     my $class = shift;
25    
26 root 1.20 my $self = $class->SUPER::new (
27 root 1.1 z => -1,
28     can_focus => 1,
29 root 1.104 tilesize => 32,
30 root 1.1 @_
31 root 1.20 );
32    
33     $self
34 root 1.1 }
35    
36 root 1.36 sub add_command {
37     my ($self, $command, $tooltip, $widget, $cb) = @_;
38    
39     (my $data = $command) =~ s/\\//g;
40    
41     $tooltip =~ s/^\s+//;
42     $tooltip = "<big>$data</big>\n\n$tooltip";
43     $tooltip =~ s/\s+$//;
44    
45 root 1.119 $::COMPLETER->{command}{$command} = [$data, $tooltip, $widget, $cb, ++$self->{command_id}];
46 root 1.36 }
47 root 1.4
48 root 1.36 sub clr_commands {
49     my ($self) = @_;
50 root 1.4
51 root 1.119 %{$::COMPLETER->{command}} = ();
52 elmex 1.76
53 root 1.119 $::COMPLETER->hide
54     if $::COMPLETER;
55 root 1.4 }
56    
57 root 1.85 sub server_login {
58     my ($server) = @_;
59 root 1.83
60     ::stop_game ();
61     local $::PROFILE->{host} = $server;
62     ::start_game ();
63     }
64    
65     sub editor_invoke {
66 root 1.85 my $editsup = $::CONN && $::CONN->{editor_support}
67     or return;
68 root 1.83
69 root 1.126 DC::background {
70 root 1.85 print "preparing editor startup...\n";
71    
72     my $server = $editsup->{gameserver} || "default";
73     $server =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge;
74    
75 root 1.124 local $ENV{CROSSFIRE_MAPDIR} = my $mapdir = "$Deliantra::VARDIR/map.$server"; mkdir $mapdir;
76     local $ENV{CROSSFIRE_LIBDIR} = my $libdir = "$Deliantra::VARDIR/lib.$server"; mkdir $libdir;
77 root 1.85
78     print "map directory is $mapdir\n";
79     print "lib directory is $libdir\n";
80    
81 root 1.126 my $ua = DC::lwp_useragent;
82 root 1.84
83 root 1.85 for my $file (qw(archetypes crossfire.0)) {
84     my $url = "$editsup->{lib_root}$file";
85     print "mirroring $url...\n";
86 root 1.126 DC::lwp_check $ua->mirror ($url, "$libdir/$file");
87 root 1.85 printf "%s size %d octets\n", $file, -s "$libdir/$file";
88     }
89    
90     if (1) { # upload a map
91     my $mapname = $::CONN->{map_info}[0];
92    
93 root 1.87 my $mappath = "$mapdir/$mapname";
94    
95     -e $mappath and die "$mappath already exists\n";
96    
97 root 1.85 print "getting map revision for $mapname...\n";
98    
99     # try to get the most recent head revision, what a hack,
100     # this should have been returned while downloading *sigh*
101 root 1.126 my $log = (DC::lwp_check $ua->get ("$editsup->{cvs_root}/$mapname?view=log&logsort=rev"))->decoded_content;
102 root 1.84
103     if ($log =~ /\?rev=(\d+\.\d+)"/) {
104     my $rev = $1;
105    
106 root 1.85 print "downloading revision $rev...\n";
107 root 1.84
108 root 1.126 my $map = (DC::lwp_check $ua->get ("$editsup->{cvs_root}/$mapname?rev=$rev"))->decoded_content;
109 root 1.84
110 root 1.86 my $meta = {
111     %$editsup,
112     path => $mapname,
113     revision => $rev,
114     cf_login => $::PROFILE->{user},
115     };
116    
117     require File::Basename;
118     require File::Path;
119    
120     File::Path::mkpath (File::Basename::dirname ($mappath));
121     open my $fh, ">:raw:perlio", "$mappath.meta"
122 root 1.87 or die "$mappath.meta: $!\n";
123 root 1.126 print $fh DC::encode_json $meta;
124 root 1.86 close $fh;
125     open my $fh, ">:raw:perlio:utf8", $mappath
126 root 1.87 or die "$mappath: $!\n";
127 root 1.86 print $fh $map;
128     close $fh;
129    
130     print "saved as $mappath\n";
131    
132     print "invoking editor...\n";
133     exec "/root/s2/gce $mappath";#d#
134    
135 root 1.85 # now upload it
136     # require HTTP::Request::Common;
137     #
138     # my $res = $ua->post (
139     # $ENV{CFPLUS_UPLOAD},
140     # Content_Type => 'multipart/form-data',
141     # Content => [
142     # path => $mapname,
143     # mapdir => $ENV{CROSSFIRE_MAPDIR},
144     # map => $map,
145     # revision => $rev,
146     # cf_login => $ENV{CFPLUS_LOGIN},
147     # cf_password => $ENV{CFPLUS_PASSWORD},
148     # comment => "",
149     # ]
150     # );
151     #
152     # if ($res->is_error) {
153     # # fatal condition
154     # warn $res->status_line;
155     # } else {
156     # # script replies are marked as {{..}}
157     # my @msgs = $res->decoded_content =~ m/\{\{(.*?)\}\}/g;
158     # warn map "$_\n", @msgs;
159     # }
160 root 1.84 } else {
161 root 1.85 die "viewvc parse error, unable to detect revision\n";
162 root 1.84 }
163     }
164     }
165 root 1.83 }
166    
167 root 1.63 sub invoke_button_down {
168 root 1.1 my ($self, $ev, $x, $y) = @_;
169    
170 root 1.71 if ($ev->{button} == 1) {
171     $self->grab_focus;
172 root 1.147 return unless $::CONN && $self->{ctilesize};
173 root 1.1
174 root 1.126 my $x = $self->{dx} + DC::floor +($ev->{x} - $self->{sx0}) / $self->{ctilesize};
175     my $y = $self->{dy} + DC::floor +($ev->{y} - $self->{sy0}) / $self->{ctilesize};
176 root 1.49
177 root 1.126 $x -= DC::floor $::MAP->w * 0.5;
178     $y -= DC::floor $::MAP->h * 0.5;
179 root 1.54
180 elmex 1.94 if ($::CONN) {
181 elmex 1.115 $::CONN->lookat ($x, $y)
182 elmex 1.94 }
183 root 1.49
184     } elsif ($ev->{button} == 2) {
185 root 1.71 $self->grab_focus;
186     return unless $::CONN;
187    
188 root 1.5 my ($ox, $oy) = ($ev->{x}, $ev->{y});
189 root 1.1 my ($bw, $bh) = ($::CFG->{map_shift_x}, $::CFG->{map_shift_y});
190    
191     $self->{motion} = sub {
192     my ($ev, $x, $y) = @_;
193    
194 root 1.5 ($x, $y) = ($ev->{x}, $ev->{y});
195 root 1.1
196     $::CFG->{map_shift_x} = $bw + $x - $ox;
197     $::CFG->{map_shift_y} = $bh + $y - $oy;
198    
199     $self->update;
200     };
201 root 1.71 } elsif ($ev->{button} == 3) {
202 root 1.85 my @items = (
203 root 1.74 ["Help Browser…\tF1", sub { $::HELP_WINDOW->toggle_visibility }],
204     ["Statistics\tF2", sub { ::toggle_player_page ($::STATS_PAGE) }],
205     ["Skills\tF3", sub { ::toggle_player_page ($::SKILL_PAGE) }],
206 root 1.75 ["Spells…\tF4", sub { ::toggle_player_page ($::SPELL_PAGE) }],
207     ["Inventory…\tF5", sub { ::toggle_player_page ($::INVENTORY_PAGE) }],
208 root 1.74 ["Setup… \tF9", sub { $::SETUP_DIALOG->toggle_visibility }],
209 root 1.129 # ["Server Messages…", sub { $::MESSAGE_WINDOW->toggle_visibility }],
210 root 1.85 );
211    
212 root 1.90 if ($::CONN && $::CONN->{editor_support}) {
213 root 1.128 # push @items, [
214     # "Edit this map <span size='xx-small'>(" . (DC::asxml $::CONN->{map_info}[0]) . ")</span>",
215     # \&editor_invoke,
216     # ];
217 root 1.85
218 root 1.161 for my $type (@{ $::CONN->{editor_support}{servertypes} }) {
219     $::CONN->{editor_support}{servertype} ne $type
220 root 1.85 or next;
221     my $server = $::CONN->{editor_support}{"${type}server"}
222     or next;
223    
224     push @items, [
225 root 1.126 "Login on $type server <span size='xx-small'>(" . (DC::asxml $server) . ")</span>",
226 root 1.85 sub { server_login $server },
227     ];
228     }
229     }
230    
231     push @items,
232     ["Quit",
233     sub {
234     if ($::CONN) {
235     &::open_quit_dialog;
236     } else {
237     exit;
238 elmex 1.79 }
239 root 1.85 }
240 root 1.71 ],
241 root 1.85 ;
242    
243 root 1.126 (new DC::UI::Menu
244 root 1.85 items => \@items,
245 root 1.71 )->popup ($ev);
246 root 1.1 }
247 root 1.47
248     1
249 root 1.1 }
250    
251 root 1.63 sub invoke_button_up {
252 root 1.1 my ($self, $ev, $x, $y) = @_;
253    
254     delete $self->{motion};
255 root 1.47
256     1
257 root 1.1 }
258    
259 root 1.63 sub invoke_mouse_motion {
260 root 1.1 my ($self, $ev, $x, $y) = @_;
261    
262 root 1.47 if ($self->{motion}) {
263     $self->{motion}->($ev, $x, $y);
264     } else {
265     return 0;
266     }
267    
268     1
269 root 1.1 }
270    
271     sub size_request {
272 root 1.104 my ($self) = @_;
273    
274 root 1.1 (
275 root 1.126 $self->{tilesize} * DC::ceil $::WIDTH / $self->{tilesize},
276     $self->{tilesize} * DC::ceil $::HEIGHT / $self->{tilesize},
277 root 1.1 )
278     }
279    
280     sub update {
281     my ($self) = @_;
282    
283     $self->{need_update} = 1;
284     $self->SUPER::update;
285     }
286    
287 root 1.36 my %DIR = (
288 root 1.142 ( "," . DC::SDLK_KP5 ), [0, "stay fire"],
289     ( "," . DC::SDLK_KP8 ), [1, "north"],
290     ( "," . DC::SDLK_KP9 ), [2, "northeast"],
291     ( "," . DC::SDLK_KP6 ), [3, "east"],
292     ( "," . DC::SDLK_KP3 ), [4, "southeast"],
293     ( "," . DC::SDLK_KP2 ), [5, "south"],
294     ( "," . DC::SDLK_KP1 ), [6, "southwest"],
295     ( "," . DC::SDLK_KP4 ), [7, "west"],
296     ( "," . DC::SDLK_KP7 ), [8, "northwest"],
297    
298     ( "," . DC::SDLK_PAGEUP ), [2, "northeast"],
299     ( "," . DC::SDLK_PAGEDOWN ), [4, "southeast"],
300     ( "," . DC::SDLK_END ), [6, "southwest"],
301     ( "," . DC::SDLK_HOME ), [8, "northwest"],
302    
303     ( "," . DC::SDLK_UP ), [1, "north"],
304     ("1," . DC::SDLK_UP ), [2, "northeast"],
305     ( "," . DC::SDLK_RIGHT ), [3, "east"],
306     ("1," . DC::SDLK_RIGHT ), [4, "southeast"],
307     ( "," . DC::SDLK_DOWN ), [5, "south"],
308     ("1," . DC::SDLK_DOWN ), [6, "southwest"],
309     ( "," . DC::SDLK_LEFT ), [7, "west"],
310     ("1," . DC::SDLK_LEFT ), [8, "northwest"],
311 root 1.36 );
312    
313 root 1.63 sub invoke_key_down {
314 root 1.36 my ($self, $ev) = @_;
315    
316     my $mod = $ev->{mod};
317     my $sym = $ev->{sym};
318     my $uni = $ev->{unicode};
319    
320 root 1.142 $mod &= DC::KMOD_CTRL | DC::KMOD_ALT | DC::KMOD_META | DC::KMOD_SHIFT;
321 root 1.71
322 root 1.130 # ignore repeated keypresses
323     return if $self->{last_mod} == $mod && $self->{last_sym} == $sym;
324     $self->{last_mod} = $mod;
325     $self->{last_sym} = $sym;
326    
327 root 1.142 my $dir = $DIR{ (!!($mod & (DC::KMOD_ALT | DC::KMOD_META))) . ",$sym" };
328    
329     if ($::CONN && $dir) {
330 root 1.126 if ($mod & DC::KMOD_SHIFT) {
331 root 1.36 $self->{shft}++;
332 root 1.112 if ($dir->[0] != $self->{fire_dir}) {
333     $::CONN->user_send ("fire $dir->[0]");
334 elmex 1.58 }
335 elmex 1.123 $self->{fire_dir} = $dir->[0];
336 root 1.126 } elsif ($mod & DC::KMOD_CTRL) {
337 root 1.36 $self->{ctrl}++;
338 root 1.112 $::CONN->user_send ("run $dir->[0]");
339 root 1.36 } else {
340 root 1.112 $::CONN->user_send ("$dir->[1]");
341 root 1.36 }
342 root 1.119
343     return 1;
344 root 1.36 }
345 root 1.47
346 root 1.119 0
347 root 1.36 }
348    
349 root 1.63 sub invoke_key_up {
350 root 1.36 my ($self, $ev) = @_;
351    
352 root 1.47 my $res = 0;
353 root 1.36 my $mod = $ev->{mod};
354     my $sym = $ev->{sym};
355    
356 root 1.131 delete $self->{last_mod};
357     delete $self->{last_sym};
358    
359 elmex 1.58 if ($::CFG->{shift_fire_stop}) {
360 root 1.126 if (!($mod & DC::KMOD_SHIFT) && delete $self->{shft}) {
361 elmex 1.58 $::CONN->user_send ("fire_stop");
362     delete $self->{fire_dir};
363     $res = 1;
364     }
365     } else {
366 root 1.142 my $dir = $DIR{ (!!($mod & (DC::KMOD_ALT | DC::KMOD_META))) . ",$sym" };
367    
368     if ($dir && delete $self->{shft}) {
369 elmex 1.58 $::CONN->user_send ("fire_stop");
370     delete $self->{fire_dir};
371     $res = 1;
372 root 1.126 } elsif (($sym == DC::SDLK_LSHIFT || $sym == DC::SDLK_RSHIFT)
373 elmex 1.123 && delete $self->{shft}) { # XXX: is RSHIFT ok?
374 elmex 1.58 $::CONN->user_send ("fire_stop");
375     delete $self->{fire_dir};
376     $res = 1;
377     }
378 root 1.36 }
379 root 1.47
380 root 1.142 if (!($mod & DC::KMOD_CTRL) && delete $self->{ctrl}) {
381 root 1.36 $::CONN->user_send ("run_stop");
382 root 1.47 $res = 1;
383 root 1.36 }
384 root 1.47
385     $res
386 root 1.36 }
387    
388 root 1.110 sub invoke_visibility_change {
389     my ($self) = @_;
390    
391     $self->refresh_hook;
392    
393     0
394     }
395    
396 root 1.133 sub set_tilesize {
397     my ($self, $tilesize) = @_;
398    
399     $self->{tilesize} = $tilesize;
400     }
401    
402     sub scroll {
403     my ($self, $dx, $dy) = @_;
404    
405     $self->movement_update;
406    
407     $self->{sdx} += $dx * $self->{tilesize}; # smooth displacement
408     $self->{sdy} += $dy * $self->{tilesize};
409 root 1.156
410 root 1.157 # save old fow texture, if applicable
411 root 1.160 $self->{prev_fow_texture} = $::CFG->{smooth_transitions} && $self->{fow_texture};
412 root 1.157 $self->{lfdx} = $dx;
413     $self->{lfdy} = $dy;
414     $self->{lmdx} = $self->{dx};
415     $self->{lmdy} = $self->{dy};
416 root 1.156
417 root 1.157 $::MAP->scroll ($dx, $dy);
418 root 1.133 }
419    
420 root 1.61 sub set_magicmap {
421     my ($self, $w, $h, $x, $y, $data) = @_;
422    
423 root 1.92 $x -= $::MAP->ox + 1 + int 0.5 * $::MAP->w;
424     $y -= $::MAP->oy + 1 + int 0.5 * $::MAP->h;
425 root 1.61
426     $self->{magicmap} = [$x, $y, $w, $h, $data];
427    
428     $self->update;
429     }
430    
431 root 1.133 sub movement_update {
432     my ($self) = @_;
433    
434     if ($::CFG->{smooth_movement}) {
435     if ($self->{sdx} || $self->{sdy}) {
436     my $diff = EV::time - ($self->{last_update} || $::LAST_REFRESH);
437     my $spd = $::CONN->{stat}{DC::Protocol::CS_STAT_SPEED};
438    
439     # the minimum time for a single tile movement
440 root 1.141 my $mintime = DC::Protocol::TICK * DC::ceil 1 / ($spd * DC::Protocol::TICK || 1);
441 root 1.133
442 root 1.145 $spd *= $self->{tilesize};
443    
444 root 1.133 # jump if "impossibly high" speed
445     if (
446     (max abs $self->{sdx}, abs $self->{sdy})
447 root 1.145 > $spd * $mintime * 2.1
448 root 1.133 ) {
449 root 1.145 #warn "jump ", (max abs $self->{sdx}, abs $self->{sdy}), " ", $spd * $mintime * 2.1;#d#
450 root 1.133 $self->{sdx} = $self->{sdy} = 0;
451     } else {
452 root 1.145 $spd *= $diff * 1.0001; # 1.0001 so that we don't accumulate rounding errors the wrong direction
453 root 1.133
454     my $dx = $self->{sdx} < 0 ? -$spd : $spd;
455     my $dy = $self->{sdy} < 0 ? -$spd : $spd;
456    
457 root 1.135 if ($self->{sdx} * ($self->{sdx} - $dx) <= 0) { $self->{sdx} = 0 } else { $self->{sdx} -= $dx }
458     if ($self->{sdy} * ($self->{sdy} - $dy) <= 0) { $self->{sdy} = 0 } else { $self->{sdy} -= $dy }
459 root 1.145 }
460 root 1.133
461 root 1.145 $self->update;
462 root 1.133 }
463     } else {
464     $self->{sdx} = $self->{sdy} = 0;
465     }
466    
467     $self->{last_update} = EV::time;
468     }
469    
470 root 1.110 sub refresh_hook {
471 root 1.1 my ($self) = @_;
472    
473 root 1.116 if ($::MAP && $::CONN) {
474 root 1.114 if (delete $self->{need_update}) {
475 root 1.133 $self->movement_update;
476    
477 root 1.114 my $tilesize = $self->{ctilesize} = (int $self->{tilesize} * $::CFG->{map_scale}) || 1;
478    
479 root 1.140 my $sdx_t = DC::ceil $self->{sdx} / $tilesize;
480     my $sdy_t = DC::ceil $self->{sdy} / $tilesize;
481 root 1.137
482 root 1.133 # width/height of map, in tiles
483 root 1.139 my $sw = $self->{sw} = 2 + DC::ceil $self->{w} / $tilesize;
484     my $sh = $self->{sh} = 2 + DC::ceil $self->{h} / $tilesize;
485 root 1.114
486 root 1.133 # the map displacement, in tiles
487 root 1.137 my $sx = DC::ceil $::CFG->{map_shift_x} / $tilesize + $sdx_t;
488     my $sy = DC::ceil $::CFG->{map_shift_y} / $tilesize + $sdy_t;
489 root 1.114
490 root 1.133 # the upper left "visible" corner, in pixels
491 root 1.114 my $sx0 = $self->{sx0} = $::CFG->{map_shift_x} - $sx * $tilesize;
492     my $sy0 = $self->{sy0} = $::CFG->{map_shift_y} - $sy * $tilesize;
493    
494 root 1.126 my $dx = $self->{dx} = DC::ceil 0.5 * ($::MAP->w - $sw) - $sx;
495     my $dy = $self->{dy} = DC::ceil 0.5 * ($::MAP->h - $sh) - $sy;
496 root 1.114
497     if ($::CFG->{fow_enable}) {
498 root 1.158 # draw_fow_texture REQUIRES the fow texture to stay the same size.
499 root 1.157 my ($w, $h, $data) = $::MAP->fow_texture ($dx, $dy, $sw, $sh);
500 root 1.114
501 root 1.126 $self->{fow_texture} = new DC::Texture
502 root 1.114 w => $w,
503     h => $h,
504     data => $data,
505 root 1.156 internalformat => GL_ALPHA,
506     format => GL_ALPHA;
507 root 1.114 } else {
508     delete $self->{fow_texture};
509     }
510 root 1.60
511 root 1.114 glNewList ($self->{list} ||= glGenList);
512 root 1.60
513 root 1.114 glPushMatrix;
514 root 1.134 glTranslate $sx0, $sy0;
515 root 1.114 glScale $::CFG->{map_scale}, $::CFG->{map_scale};
516 root 1.149 glTranslate DC::ceil $self->{sdx}, DC::ceil $self->{sdy};
517 root 1.91
518 root 1.132 $::MAP->draw ($dx, $dy, $sw, $sh,
519 root 1.163 ($self->{tilesize}) x 2,
520 root 1.133 $::CONN->{player}{tag},
521     -$self->{sdx}, -$self->{sdy});
522 root 1.60
523 root 1.114 glScale $self->{tilesize}, $self->{tilesize};
524 root 1.59
525 root 1.114 if (my $tex = $self->{fow_texture}) {
526 root 1.160 my @prev_fow_params;
527    
528     if ($DC::OpenGL::GL_MULTITEX && $self->{prev_fow_texture}) {
529 root 1.157 my $d1 = DC::distance $self->{sdx}, $self->{sdy};
530     my $d2 = (DC::distance $self->{lfdx}, $self->{lfdy}) * $tilesize;
531 root 1.151
532     if ($d1 * $d2) {
533 root 1.160 @prev_fow_params = (
534 root 1.157 (min 1, $d1 / $d2),
535     $self->{lmdx} - $dx - $self->{lfdx},
536     $self->{lmdy} - $dy - $self->{lfdy},
537 root 1.160 @{$self->{prev_fow_texture}}{qw(name data)}
538     );
539 root 1.157 }
540     }
541 root 1.156
542 root 1.160 DC::Texture::draw_fow_texture
543     $::CFG->{fow_intensity},
544     $TEX_HIDDEN[$::CFG->{fow_texture}]{name},
545     @{$self->{fow_texture}}{qw(name data s t w h)},
546     @prev_fow_params;
547 root 1.114 }
548 root 1.1
549 root 1.114 if ($self->{magicmap}) {
550     my ($x, $y, $w, $h, $data) = @{ $self->{magicmap} };
551 root 1.31
552 root 1.114 $x += $::MAP->ox + $self->{dx};
553     $y += $::MAP->oy + $self->{dy};
554 root 1.1
555 root 1.114 glTranslate - $x - 1, - $y - 1;
556     glBindTexture GL_TEXTURE_2D, $magicmap_tex->{name};
557 root 1.157 $::MAP->draw_magicmap ($w, $h, $data);
558 root 1.114 }
559 root 1.1
560 root 1.109 glPopMatrix;
561 root 1.114 glEndList;
562 root 1.1 }
563 root 1.114 } else {
564 root 1.158 delete $self->{last_fow_texture};
565     delete $self->{fow_texture};
566    
567 root 1.114 glDeleteList delete $self->{list}
568     if $self->{list};
569 root 1.1 }
570 root 1.110 }
571    
572     sub draw {
573     my ($self) = @_;
574    
575     $self->{root}->on_post_alloc (prepare => sub { $self->refresh_hook });
576    
577     return unless $self->{list};
578    
579 root 1.126 my $focused = $DC::UI::FOCUS == $self
580     || $DC::UI::FOCUS == $::COMPLETER->{entry};
581 root 1.110
582     return
583     unless $focused || !$::FAST;
584    
585 root 1.1 glCallList $self->{list};
586    
587 root 1.29 # TNT2 emulates logops in software (or worse :)
588 root 1.60 unless ($focused) {
589 root 1.120 glColor_premultiply 0, 0, 1, 0.25;
590 root 1.29 glEnable GL_BLEND;
591 root 1.120 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
592 root 1.1 glBegin GL_QUADS;
593     glVertex 0, 0;
594     glVertex 0, $::HEIGHT;
595     glVertex $::WIDTH, $::HEIGHT;
596     glVertex $::WIDTH, 0;
597     glEnd;
598 root 1.29 glDisable GL_BLEND;
599 root 1.1 }
600     }
601    
602 root 1.36 sub DESTROY {
603     my $self = shift;
604 root 1.3
605 root 1.36 glDeleteList $self->{list};
606 root 1.1
607 root 1.36 $self->SUPER::DESTROY;
608 root 1.8 }
609    
610 root 1.126 package DC::MapWidget::MapMap;
611 root 1.18
612 root 1.162 use common::sense;
613 root 1.105
614 root 1.126 our @ISA = DC::UI::Base::;
615 root 1.18
616 root 1.126 use DC::OpenGL;
617 root 1.18
618     sub size_request {
619 root 1.111 ($::HEIGHT * 0.2, $::HEIGHT * 0.2)
620 root 1.18 }
621    
622 root 1.110 sub refresh_hook {
623     my ($self) = @_;
624    
625 root 1.163 if ($::MAP && $self->{texture_atime} < EV::now) {
626 root 1.110 my ($w, $h) = @$self{qw(w h)};
627    
628 root 1.144 return unless $w && $h;
629    
630 root 1.110 my $sw = int $::WIDTH / ($::MAPWIDGET->{tilesize} * $::CFG->{map_scale}) + 0.99;
631     my $sh = int $::HEIGHT / ($::MAPWIDGET->{tilesize} * $::CFG->{map_scale}) + 0.99;
632    
633     my $ox = 0.5 * ($w - $sw);
634     my $oy = 0.5 * ($h - $sh);
635    
636     my $sx = int $::CFG->{map_shift_x} / $::MAPWIDGET->{tilesize};
637     my $sy = int $::CFG->{map_shift_y} / $::MAPWIDGET->{tilesize};
638    
639     #TODO: map scale is completely borked
640    
641     my $x0 = int $ox - $sx + 0.5;
642     my $y0 = int $oy - $sy + 0.5;
643    
644     $self->{sw} = $sw;
645     $self->{sh} = $sh;
646    
647     $self->{x0} = $x0;
648     $self->{y0} = $y0;
649    
650 root 1.163 $self->{texture_atime} = EV::now + 1/2;
651 root 1.110
652     $self->{texture} =
653 root 1.126 new DC::Texture
654 root 1.110 w => $w,
655     h => $h,
656     data => $::MAP->mapmap (-$ox, -$oy, $w, $h),
657 root 1.126 type => $DC::GL_VERSION >= 1.2 ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE;
658 root 1.110 }
659     }
660    
661     sub invoke_visibility_change {
662     my ($self) = @_;
663    
664     $self->refresh_hook;
665    
666     0
667     }
668    
669 root 1.63 sub invoke_size_allocate {
670 root 1.18 my ($self, $w, $h) = @_;
671    
672     $self->update;
673 root 1.63
674     1
675 root 1.18 }
676    
677     sub update {
678     my ($self) = @_;
679    
680     delete $self->{texture_atime};
681     $self->SUPER::update;
682     }
683    
684     sub _draw {
685     my ($self) = @_;
686    
687 root 1.110 $self->{root}->on_post_alloc (texture => sub { $self->refresh_hook });
688 root 1.18
689 root 1.110 $self->{texture} or return;
690 root 1.18
691     glEnable GL_BLEND;
692     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
693     glEnable GL_TEXTURE_2D;
694     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
695    
696     $self->{texture}->draw_quad (0, 0);
697    
698     glDisable GL_TEXTURE_2D;
699    
700     glTranslate 0.375, 0.375;
701    
702     glColor 1, 1, 0, 1;
703     glBegin GL_LINE_LOOP;
704 root 1.110 glVertex $self->{x0} , $self->{y0} ;
705     glVertex $self->{x0} , $self->{y0} + $self->{sh};
706     glVertex $self->{x0} + $self->{sw}, $self->{y0} + $self->{sh};
707     glVertex $self->{x0} + $self->{sw}, $self->{y0} ;
708 root 1.18 glEnd;
709    
710     glDisable GL_BLEND;
711     }
712    
713 root 1.126 package DC::MapWidget::Command;
714 root 1.8
715 root 1.162 use common::sense;
716 root 1.8
717 root 1.126 use DC::OpenGL;
718 root 1.8
719 root 1.126 our @ISA = DC::UI::Frame::;
720 root 1.8
721     sub new {
722     my $class = shift;
723    
724     my $self = $class->SUPER::new (
725 root 1.23 bg => [0, 0, 0, 0.8],
726 root 1.8 @_,
727 root 1.23 );
728    
729 root 1.126 $self->add ($self->{vbox} = new DC::UI::VBox);
730 root 1.23
731     $self->{label} = [
732     map
733 root 1.126 DC::UI::Label->new (
734 root 1.127 align => 0,
735 root 1.25 can_hover => 1,
736     can_events => 1,
737     tooltip_width => 0.33,
738     fontsize => $_,
739 root 1.27 ), (0.8) x 16
740 root 1.23 ];
741    
742 root 1.126 $self->{entry} = new DC::UI::Entry
743 root 1.40 on_changed => sub {
744 root 1.23 $self->update_labels;
745 root 1.63 0
746 root 1.36 },
747 elmex 1.57 on_button_down => sub {
748     my ($entry, $ev, $x, $y) = @_;
749    
750     if ($ev->{button} == 3) {
751 root 1.126 (new DC::UI::Menu
752 elmex 1.57 items => [
753 root 1.126 ["bind <i>" . (DC::asxml $self->{select}) . "</i> to a key"
754     => sub { DC::Macro::quick_macro [$self->{select}], sub { $entry->grab_focus } }]
755 elmex 1.57 ],
756     )->popup ($ev);
757     return 1;
758     }
759     0
760     },
761 root 1.40 on_key_down => sub {
762 root 1.36 my ($entry, $ev) = @_;
763    
764     my $self = $entry->{parent}{parent};
765    
766     if ($ev->{sym} == 13) {
767     if (exists $self->{select}) {
768 root 1.37 $self->{last_command} = $self->{select};
769 root 1.36 $::CONN->user_send ($self->{select});
770 elmex 1.52
771 elmex 1.68 unshift @{$self->{history}}, $self->{entry}->get_text;
772 elmex 1.52 $self->{hist_ptr} = 0;
773    
774 root 1.36 $self->hide;
775     }
776     } elsif ($ev->{sym} == 27) {
777 elmex 1.52 $self->{hist_ptr} = 0;
778 root 1.36 $self->hide;
779 root 1.126 } elsif ($ev->{sym} == DC::SDLK_DOWN) {
780 elmex 1.52 if ($self->{hist_ptr} > 1) {
781     $self->{hist_ptr}--;
782     $self->{entry}->set_text ($self->{history}->[$self->{hist_ptr} - 1]);
783     } elsif ($self->{hist_ptr} > 0) {
784     $self->{hist_ptr}--;
785     $self->{entry}->set_text ($self->{hist_saveback});
786     } else {
787     ++$self->{select_offset}
788     if $self->{select_offset} < $#{ $self->{last_match} || [] };
789     }
790 root 1.36 $self->update_labels;
791 root 1.126 } elsif ($ev->{sym} == DC::SDLK_UP) {
792 elmex 1.52 if ($self->{select_offset}) {
793     --$self->{select_offset}
794     } else {
795     unless ($self->{hist_ptr}) {
796     $self->{hist_saveback} = $self->{entry}->get_text;
797     }
798     if ($self->{hist_ptr} <= $#{$self->{history}}) {
799     $self->{hist_ptr}++;
800     }
801     $self->{entry}->set_text ($self->{history}->[$self->{hist_ptr} - 1])
802     if exists $self->{history}->[$self->{hist_ptr} - 1];
803     }
804 root 1.36 $self->update_labels;
805     } else {
806     return 0;
807     }
808    
809     1
810     }
811     ;
812 root 1.23
813     $self->{vbox}->add (
814     $self->{entry},
815     @{$self->{label}},
816 root 1.8 );
817    
818     $self
819     }
820    
821 root 1.36 sub set_prefix {
822     my ($self, $prefix) = @_;
823    
824     $self->{entry}->set_text ($prefix);
825     $self->show;
826     }
827    
828 root 1.63 sub invoke_size_allocate {
829 root 1.8 my ($self, $w, $h) = @_;
830    
831 root 1.44 $self->move_abs (($::WIDTH - $w) * 0.5, ($::HEIGHT - $h) * 0.6, 10);
832 root 1.63
833     $self->SUPER::invoke_size_allocate ($w, $h)
834 root 1.8 }
835    
836 root 1.36 sub show {
837     my ($self) = @_;
838    
839     $self->SUPER::show;
840 root 1.63 $self->{entry}->grab_focus;
841 root 1.36 }
842    
843     sub hide {
844     my ($self) = @_;
845    
846 elmex 1.76 $self->{hist_ptr} = 0;
847    
848 root 1.36 $self->SUPER::hide;
849     $self->{entry}->set_text ("");
850     }
851    
852 root 1.77 sub inject_key_down {
853 root 1.23 my ($self, $ev) = @_;
854    
855 root 1.77 $self->{entry}->grab_focus;
856     $self->{entry}->emit (key_down => $ev);
857 root 1.23 }
858    
859 root 1.8 sub update_labels {
860     my ($self) = @_;
861    
862 root 1.23 my $text = $self->{entry}->get_text;
863    
864     length $text
865 root 1.36 or return $self->hide;
866 root 1.23
867 root 1.36 if ($text ne $self->{last_search}) {
868     my @match;
869 root 1.23
870 root 1.36 if ($text =~ /^(.*?)\s+$/) {
871 elmex 1.150 my ($cmd, $arg) = $text =~ /^\s*([^[:space:]]*)(.*)$/;
872     @match = ([[$cmd,'(appended whitespace suppresses completion)'],$text]);
873 root 1.36 } else {
874 elmex 1.150 # @match is [command, penalty, command with arguments] until sort
875    
876     my ($cmd, $arg) = $text =~ /^\s*([^[:space:]]*)(.*)$/;
877    
878     my $regexp_abbrev = do {
879 root 1.36 my ($beg, @chr) = split //, lc $cmd;
880 root 1.23
881 root 1.36 # the following regex is used to match our "completion entry"
882     # to an actual command - the parentheses match kind of "overhead"
883     # - the more characters the parentheses match, the less attractive
884     # is the match.
885     my $regexp = "^\Q$beg\E"
886     . join "", map "(?:.*?[ \\\\]\Q$_\E|(.*?)\Q$_\E)", @chr;
887     qr<$regexp>
888     };
889    
890 elmex 1.150 my $regexp_partial = do {
891     my $regexp = "^\Q$text\E(.*)";
892     qr<$regexp>
893     };
894 root 1.36
895     for (keys %{$self->{command}}) {
896 elmex 1.150 my @scores;
897    
898     # 1. Complete command [with args]
899     # command is a prefix of the text
900     # score is length of complete command matched
901     # e.g. "invoke summon pet monster bat"
902     # "invoke" "summon pet monster bat" = 6
903     # "invoke summon pet monster" "bat" = 25
904     if ($text =~ /^\Q$_\E(.*)/) {
905     push @scores, [$_, length $_, $text];
906 root 1.36 }
907 elmex 1.150
908     # 2. Partial command
909     # text is a prefix of the full command
910     # score is the length of the input text
911     # e.g. "invoke s"
912     # "invoke small fireball" = 8
913     # "invoke summon pet monster" = 8
914    
915     if ($_ =~ $regexp_partial) {
916     push @scores, [$_, length $text, $_];
917     }
918    
919     # 3. Abbreviation match
920     # attempts to use first word of text as an abbreviated command
921     # score is length of word + 1 - 3 per non-word-initial character
922    
923     if (my @penalty = $_ =~ $regexp_abbrev) {
924     push @scores, [$_, (length $cmd) + 1 - (length join "", map "::$_", grep defined, @penalty), "$_$arg"];
925     }
926    
927     # Pick the best option for this command
928     push @match, (sort {
929     $b->[1] <=> $a->[1]
930     } @scores)[0];
931 root 1.23 }
932 root 1.36
933 elmex 1.150 # @match is now [command object, command with arguments]
934     @match = map [$self->{command}{$_->[0]}, $_->[2]],
935 root 1.36 sort {
936 elmex 1.150 $b->[1] <=> $a->[1]
937 root 1.36 or $self->{command}{$a->[0]}[4] <=> $self->{command}{$b->[0]}[4]
938 root 1.39 or (length $b->[0]) <=> (length $a->[0])
939 root 1.36 } @match;
940 root 1.8 }
941 root 1.23
942 root 1.39 $self->{last_search} = $text;
943 root 1.23 $self->{last_match} = \@match;
944    
945     $self->{select_offset} = 0;
946 root 1.8 }
947    
948 root 1.23 my @labels = @{ $self->{label} };
949     my @matches = @{ $self->{last_match} || [] };
950 root 1.8
951 root 1.23 if ($self->{select_offset}) {
952     splice @matches, 0, $self->{select_offset}, ();
953 root 1.8
954 root 1.23 my $label = shift @labels;
955     $label->set_text ("...");
956     $label->set_tooltip ("Use Cursor-Up to view previous matches");
957 root 1.8 }
958    
959 root 1.23 for my $label (@labels) {
960     $label->{fg} = [1, 1, 1, 1];
961     $label->{bg} = [0, 0, 0, 0];
962     }
963    
964     if (@matches) {
965 elmex 1.150 $self->{select} = "$matches[0][1]";
966 root 1.23
967     $labels[0]->{fg} = [0, 0, 0, 1];
968     $labels[0]->{bg} = [1, 1, 1, 0.8];
969     } else {
970 elmex 1.150 $self->{select} = "$text";
971 root 1.23 }
972    
973     for my $match (@matches) {
974     my $label = shift @labels;
975    
976     if (@labels) {
977 elmex 1.150 $label->set_text ("$match->[1]");
978     $label->set_tooltip ("$match->[0][1]");
979 root 1.23 } else {
980     $label->set_text ("...");
981     $label->set_tooltip ("Use Cursor-Down to view more matches");
982     last;
983     }
984     }
985 root 1.8
986 root 1.23 for my $label (@labels) {
987     $label->set_text ("");
988     $label->set_tooltip ("");
989 root 1.8 }
990    
991 root 1.23 $self->update;
992 root 1.8 }
993    
994 root 1.23 sub _draw {
995     my ($self) = @_;
996 root 1.8
997 root 1.23 # hack
998 root 1.126 local $DC::UI::FOCUS = $self->{entry};
999 root 1.10
1000 root 1.23 $self->SUPER::_draw;
1001 root 1.2 }
1002    
1003 root 1.1 1
1004 root 1.85