ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/00_map_handling.ext
Revision: 1.12
Committed: Sun Dec 31 22:23:12 2006 UTC (17 years, 4 months ago) by root
Branch: MAIN
Changes since 1.11: +11 -4 lines
Log Message:
- random maps seem to work now
- had to move map parameters into files because we need constant-sized map path lengths
  as the full map stack history would have to be included.

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.8 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.8 warn "load_map_sync<$path, $origin>\n";#d#
228 root 1.1
229 root 1.9 cf::abort if $path =~ /CVS/;#d#
230    
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 root 1.9 return;#d#
278 root 1.1 # TODO: safety, remove and allow resettable per-player maps
279     return if $map->{path}{user_rel};#d#
280     return unless $map->reset_timeout;
281    
282 root 1.9 my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access;
283 root 1.1
284     $time + $map->reset_timeout < $cf::RUNTIME
285     }
286    
287     sub cf::map::reset {
288     my ($self) = @_;
289    
290     return if $self->players;
291 root 1.3 return if $self->{path}{user_rel};#d#
292 root 1.1
293     warn "resetting map ", $self->path;#d#
294 root 1.9 return;#d#
295 root 1.3
296     utf8::encode (my $save = $self->{path}->save_path);
297     aioreq_pri 3; IO::AIO::aio_unlink $save;
298     aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
299 root 1.1
300     $self->clear;
301     $self->in_memory (cf::MAP_SWAPPED);
302 root 1.3 utf8::encode ($self->{load_path} = $self->{path}->load_path);
303 root 1.1 }
304    
305     sub cf::object::player::enter_exit {
306     my ($ob, $exit) = @_;
307    
308 root 1.6 return unless $ob->type == cf::PLAYER;
309    
310     my ($oldmap, $oldx, $oldy) = ($ob->map, $ob->x, $ob->y);
311    
312 root 1.9 $ob->enter_map ($LINK_MAP, 20, 20);
313     $ob->deactivate_recursive;
314    
315     (Coro::async {
316 root 1.3 my ($map, $x, $y);
317 root 1.9 unless (eval {
318 root 1.1
319 root 1.9 prepare_random_map $exit
320     if $exit->slaying eq "/!";
321 root 1.1
322 root 1.9 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
323 root 1.8
324 root 1.9 $map = cf::map::find_map $path->as_string;
325     $map = $map->customise_for ($ob) if $map;
326     ($x, $y) = ($exit->stats->hp, $exit->stats->sp);
327 root 1.8
328 root 1.9 unless ($map) {
329     $ob->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
330 root 1.7
331 root 1.9 # restore original map position
332     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
333 root 1.7
334 root 1.9 unless ($map) {
335     $map = cf::map::find_map $emergency_position->[0]
336     or die "FATAL: cannot load emergency map\n";
337     $x = $emergency_position->[1];
338     $y = $emergency_position->[2];
339     }
340 root 1.7 }
341 root 1.1
342 root 1.9 # use -1, -1 as default coordinates, not 0, 0
343     ($x, $y) = ($map->enter_x, $map->enter_y)
344     if $x <=0 && $y <= 0;
345    
346     warn "entering ", $map->path, " at ($x, $y)\n";#d#
347     $map->load;
348     1;
349     }) {
350     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
351    
352     $ob->message ("Something went wrong within the server. "
353     . "I'll try to bring you back to the map you were before. "
354     . "Please report this to the dungeon master",
355     cf::NDI_UNIQUE | cf::NDI_RED);
356 root 1.7
357 root 1.9 warn "ERROR in enter_exit: $@";
358     }
359     $ob->activate_recursive;
360 root 1.6 $ob->enter_map ($map, $x, $y);
361 root 1.9 })->prio (1);
362 root 1.1 }
363    
364     sub cf::map::customise_for {
365     my ($map, $ob) = @_;
366    
367     if ($map->per_player) {
368 root 1.8 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
369 root 1.1 }
370    
371     $map
372     }
373    
374     sub cf::map::emergency_save {
375     local $cf::FREEZE = 1;
376    
377 root 1.4 warn "enter emergency map save\n";
378    
379 root 1.9 cf::sync_job {
380 root 1.4 warn "begin emergency map save\n";
381     $_->save for values %cf::MAP;
382     };
383    
384 root 1.1 warn "end emergency map save\n";
385     }
386