ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/00_map_handling.ext
Revision: 1.13
Committed: Sun Dec 31 22:36:17 2006 UTC (17 years, 4 months ago) by root
Branch: MAIN
Changes since 1.12: +3 -5 lines
Log Message:
minor adjustments/fixes

File Contents

# User Rev Content
1 root 1.1 #! perl
2    
3     # why this is an extension is a good question. next question.
4    
5     use Fcntl;
6    
7     use Coro;
8     use Coro::AIO;
9    
10     our $emergency_position = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37];
11    
12     our $DEACTIVATE_TIMEOUT = 60; # number of seconds after which maps get deactivated to save cpu
13     our $SWAP_TIMEOUT = 600; # number of seconds after which maps inactive get swapped out
14     our $SCHEDULE_INTERVAL = 8; # time the map scheduler sleeps between runs
15     our $SAVE_TIMEOUT = 60; # save maps every n seconds
16     our $SAVE_INTERVAL = 0.4; # save at max. one map every $SAVE_HOLD
17     our $MAX_RESET = 7200;
18    
19     $DEACTIVATE_TIMEOUT = 3;#d#
20     $SWAP_TIMEOUT = 5;#d#
21     $SCHEDULE_INTERVAL = 1;
22    
23     our $SCHEDULER = cf::coro {
24 root 1.8 Coro::Timer::sleep 3600;#d#TODO#for debugging only
25 root 1.1 while () {
26     Coro::Timer::sleep $SCHEDULE_INTERVAL;
27    
28     for my $map (values %cf::MAP) {
29     eval {
30     next if $map->in_memory != cf::MAP_IN_MEMORY;
31 root 1.6 next if $map->players;
32 root 1.1 my $last_access = $map->last_access;
33     # not yet, because maps might become visible to players nearby
34     # we need a tiled meta map for this to work
35     # if ($last_access + $DEACTIVATE_TIMEOUT <= $cf::RUNTIME) {
36     # $map->deactivate;
37     # delete $map->{active};
38     # }
39     if ($map->should_reset) {
40     $map->reset;
41     } elsif ($last_access + $SWAP_TIMEOUT <= $cf::RUNTIME) {
42     $map->swap_out;
43     Coro::Timer::sleep $SAVE_INTERVAL;
44     } elsif ($map->{last_save} + $SAVE_TIMEOUT <= $cf::RUNTIME) {
45     $map->save;
46     Coro::Timer::sleep $SAVE_INTERVAL;
47     }
48     };
49     warn $@ if $@;
50     cede;
51     }
52     }
53     };
54     $SCHEDULER->prio (-2);
55    
56 root 1.8 sub generate_random_map {
57     my ($path, $rmp) = @_;
58    
59 root 1.11 # mit "rum" bekleckern, nicht
60 root 1.8 cf::map::_create_random_map
61     $path,
62     $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
63     $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
64     $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
65     $rmp->{exit_on_final_map},
66     $rmp->{xsize}, $rmp->{ysize},
67     $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
68     $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
69     $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
70     $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
71     $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
72 root 1.11 (cf::region::find $rmp->{region})
73 root 1.8 }
74    
75     sub parse_random_map_params {
76     my ($spec) = @_;
77    
78 root 1.9 my $rmp = { # defaults
79     xsize => 10,
80     ysize => 10,
81     };
82 root 1.8
83     for (split /\n/, $spec) {
84     my ($k, $v) = split /\s+/, $_, 2;
85    
86     $rmp->{lc $k} = $v if (length $k) && (length $v);
87     }
88    
89     $rmp
90     }
91    
92     sub prepare_random_map {
93     my ($exit) = @_;
94    
95 root 1.12 # all this does is basically replace the /! path by
96 root 1.9 # a new random map path (?random/...) with a seed
97     # that depends on the exit object
98    
99 root 1.8 my $rmp = parse_random_map_params $exit->msg;
100    
101 root 1.9 if ($exit->map) {
102 root 1.11 $rmp->{region} = $exit->map->region_name;
103 root 1.9 $rmp->{origin_map} = $exit->map->path;
104     $rmp->{origin_x} = $exit->x;
105     $rmp->{origin_y} = $exit->y;
106     }
107 root 1.1
108 root 1.12 $rmp->{random_seed} ||= $exit->random_seed;
109 root 1.1
110 root 1.12 my $data = cf::to_json $rmp;
111     my $md5 = Digest::MD5::md5_hex $data;
112    
113     if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
114     aio_write $fh, 0, (length $data), $data, 0;
115    
116     $exit->slaying ("?random/$md5");
117     $exit->msg (undef);
118     }
119 root 1.1 }
120    
121     # and all this just because we cannot iterate over
122     # all maps in C++...
123     sub cf::map::change_all_map_light {
124     my ($change) = @_;
125    
126     $_->change_map_light ($change) for values %cf::MAP;
127     }
128    
129     sub try_load_header($) {
130     my ($path) = @_;
131    
132     utf8::encode $path;
133     aio_open $path, O_RDONLY, 0
134     or return;
135    
136     my $map = cf::map::new
137     or return;
138    
139     $map->load_header ($path)
140     or return;
141    
142     $map->reset_time (0) if $map->reset_time > $cf::RUNTIME;
143     $map->reset_timeout (10);#d#
144    
145     $map->{load_path} = $path;
146    
147     $map
148     }
149    
150 root 1.8 sub cf::map::find_map {
151 root 1.1 my ($path, $origin) = @_;
152    
153 root 1.13 #warn "find_map<$path,$origin>\n";#d#
154 root 1.3
155 root 1.1 $path = ref $path ? $path : new cf::path $path, $origin && $origin->path;
156     my $key = $path->as_string;
157    
158     $cf::MAP{$key} || do {
159     # do it the slow way
160     my $map = try_load_header $path->save_path;
161    
162     if (!$map) {
163 root 1.9 if (my $rmp = $path->random_map_params) {
164     $map = generate_random_map $key, $rmp;
165     } else {
166     $map = try_load_header $path->load_path;
167     }
168    
169     $map or return;
170 root 1.8
171 root 1.9 $map->{reset_time} = $cf::RUNTIME + $map->reset_timeout;
172 root 1.8 $map->instantiate;
173    
174     # per-player maps become, after loading, normal maps
175     $map->per_player (0) if $path->{user_rel};
176 root 1.1 }
177    
178     $map->path ($key);
179     $map->{path} = $path;
180    
181     $map->reset if $map->should_reset;
182    
183     $cf::MAP{$key} = $map
184     }
185     }
186    
187 root 1.8 sub cf::map::load {
188 root 1.1 my ($self) = @_;
189    
190 root 1.8 return if $self->in_memory != cf::MAP_SWAPPED;
191 root 1.1
192     $self->in_memory (cf::MAP_LOADING);
193    
194     my $path = $self->{path};
195    
196     $self->alloc;
197     $self->load_objects ($self->{load_path}, 1)
198     or return;
199    
200     if (my $uniq = $path->uniq_path) {
201     utf8::encode $uniq;
202     if (aio_open $uniq, O_RDONLY, 0) {
203     $self->clear_unique_items;
204     $self->load_objects ($uniq, 0);
205     }
206     }
207    
208     # now do the right thing for maps
209     $self->link_multipart_objects;
210 root 1.10
211     unless ($self->{path}->is_style_map) {
212     $self->fix_auto_apply;
213     $self->decay_objects;
214     $self->update_buttons;
215     $self->set_darkness_map;
216     $self->difficulty ($self->estimate_difficulty)
217     unless $self->difficulty;
218     $self->activate;
219     }
220 root 1.1
221     $self->in_memory (cf::MAP_IN_MEMORY);
222     }
223    
224 root 1.8 sub cf::map::load_map_sync {
225     my ($path, $origin) = @_;
226 root 1.1
227 root 1.13 #warn "load_map_sync<$path, $origin>\n";#d#
228 root 1.9
229     cf::sync_job {
230 root 1.8 my $map = cf::map::find_map $path, $origin
231     or return;
232     $map->load;
233     $map
234     }
235 root 1.1 }
236    
237     sub cf::map::save {
238     my ($self) = @_;
239    
240     my $save = $self->{path}->save_path; utf8::encode $save;
241     my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
242    
243 root 1.9 $self->{last_save} = $cf::RUNTIME;
244    
245     return unless $self->dirty;
246    
247     $self->{load_path} = $save;
248    
249 root 1.10 return if $self->{path}->is_style_map;
250 root 1.9
251     warn "saving map ", $self->path;
252    
253 root 1.1 if ($uniq) {
254     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
255     $self->save_objects ($uniq, cf::IO_UNIQUES);
256     } else {
257     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
258     }
259     }
260    
261 root 1.2 sub cf::map::swap_out {
262     my ($self) = @_;
263    
264 root 1.6 return if $self->players;
265 root 1.8 return if $self->in_memory != cf::MAP_IN_MEMORY;
266 root 1.6
267 root 1.8 $self->save;
268 root 1.2 $self->clear;
269     $self->in_memory (cf::MAP_SWAPPED);
270     }
271    
272 root 1.1 sub cf::map::should_reset {
273     my ($map) = @_;
274    
275 root 1.9 return;#d#
276 root 1.1 # TODO: safety, remove and allow resettable per-player maps
277     return if $map->{path}{user_rel};#d#
278     return unless $map->reset_timeout;
279    
280 root 1.9 my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access;
281 root 1.1
282     $time + $map->reset_timeout < $cf::RUNTIME
283     }
284    
285     sub cf::map::reset {
286     my ($self) = @_;
287    
288     return if $self->players;
289 root 1.3 return if $self->{path}{user_rel};#d#
290 root 1.1
291     warn "resetting map ", $self->path;#d#
292 root 1.9 return;#d#
293 root 1.3
294     utf8::encode (my $save = $self->{path}->save_path);
295     aioreq_pri 3; IO::AIO::aio_unlink $save;
296     aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
297 root 1.1
298     $self->clear;
299     $self->in_memory (cf::MAP_SWAPPED);
300 root 1.3 utf8::encode ($self->{load_path} = $self->{path}->load_path);
301 root 1.1 }
302    
303     sub cf::object::player::enter_exit {
304     my ($ob, $exit) = @_;
305    
306 root 1.6 return unless $ob->type == cf::PLAYER;
307    
308     my ($oldmap, $oldx, $oldy) = ($ob->map, $ob->x, $ob->y);
309    
310 root 1.9 $ob->enter_map ($LINK_MAP, 20, 20);
311     $ob->deactivate_recursive;
312    
313     (Coro::async {
314 root 1.3 my ($map, $x, $y);
315 root 1.9 unless (eval {
316 root 1.1
317 root 1.9 prepare_random_map $exit
318     if $exit->slaying eq "/!";
319 root 1.1
320 root 1.9 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
321 root 1.8
322 root 1.9 $map = cf::map::find_map $path->as_string;
323     $map = $map->customise_for ($ob) if $map;
324     ($x, $y) = ($exit->stats->hp, $exit->stats->sp);
325 root 1.8
326 root 1.9 unless ($map) {
327     $ob->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
328 root 1.7
329 root 1.9 # restore original map position
330     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
331 root 1.7
332 root 1.9 unless ($map) {
333     $map = cf::map::find_map $emergency_position->[0]
334     or die "FATAL: cannot load emergency map\n";
335     $x = $emergency_position->[1];
336     $y = $emergency_position->[2];
337     }
338 root 1.7 }
339 root 1.1
340 root 1.9 # use -1, -1 as default coordinates, not 0, 0
341     ($x, $y) = ($map->enter_x, $map->enter_y)
342     if $x <=0 && $y <= 0;
343    
344     warn "entering ", $map->path, " at ($x, $y)\n";#d#
345     $map->load;
346     1;
347     }) {
348     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
349    
350 root 1.13 $ob->message ("Something went wrong deep within the crossfire server. "
351 root 1.9 . "I'll try to bring you back to the map you were before. "
352     . "Please report this to the dungeon master",
353     cf::NDI_UNIQUE | cf::NDI_RED);
354 root 1.7
355 root 1.9 warn "ERROR in enter_exit: $@";
356     }
357     $ob->activate_recursive;
358 root 1.6 $ob->enter_map ($map, $x, $y);
359 root 1.9 })->prio (1);
360 root 1.1 }
361    
362     sub cf::map::customise_for {
363     my ($map, $ob) = @_;
364    
365     if ($map->per_player) {
366 root 1.8 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
367 root 1.1 }
368    
369     $map
370     }
371    
372     sub cf::map::emergency_save {
373     local $cf::FREEZE = 1;
374    
375 root 1.4 warn "enter emergency map save\n";
376    
377 root 1.9 cf::sync_job {
378 root 1.4 warn "begin emergency map save\n";
379     $_->save for values %cf::MAP;
380     };
381    
382 root 1.1 warn "end emergency map save\n";
383     }
384