ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/00_map_handling.ext
Revision: 1.15
Committed: Mon Jan 1 11:21:54 2007 UTC (17 years, 4 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.14: +0 -0 lines
State: FILE REMOVED
Log Message:
- integrated most of the map/exit handling into cf.pm
  (it grows too large, should be split 'somehow', but thats not easy)
- moved the swap/reste scheduler into an extension
- imrpoved exit/sync logic

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