ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/00_map_handling.ext
Revision: 1.11
Committed: Sun Dec 31 21:02:04 2006 UTC (17 years, 4 months ago) by root
Branch: MAIN
Changes since 1.10: +3 -153 lines
Log Message:
more use of shstr where it makes sense naturally

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.9 # all this does is basically rpelace the /! path by
96     # 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.9 $rmp->{random_seed} = $exit->random_seed;
109 root 1.1
110 root 1.9 $exit->slaying ("?random/" . cf::to_json $rmp);
111     $exit->msg (undef);
112 root 1.1 }
113    
114     # and all this just because we cannot iterate over
115     # all maps in C++...
116     sub cf::map::change_all_map_light {
117     my ($change) = @_;
118    
119     $_->change_map_light ($change) for values %cf::MAP;
120     }
121    
122     sub try_load_header($) {
123     my ($path) = @_;
124    
125     utf8::encode $path;
126     aio_open $path, O_RDONLY, 0
127     or return;
128    
129     my $map = cf::map::new
130     or return;
131    
132     $map->load_header ($path)
133     or return;
134    
135     $map->reset_time (0) if $map->reset_time > $cf::RUNTIME;
136     $map->reset_timeout (10);#d#
137    
138     $map->{load_path} = $path;
139    
140     $map
141     }
142    
143 root 1.8 sub cf::map::find_map {
144 root 1.1 my ($path, $origin) = @_;
145    
146 root 1.8 warn "find_map<$path,$origin>\n";#d#
147 root 1.3
148 root 1.1 $path = ref $path ? $path : new cf::path $path, $origin && $origin->path;
149     my $key = $path->as_string;
150    
151     $cf::MAP{$key} || do {
152     # do it the slow way
153     my $map = try_load_header $path->save_path;
154    
155     if (!$map) {
156 root 1.9 if (my $rmp = $path->random_map_params) {
157     $map = generate_random_map $key, $rmp;
158     } else {
159     $map = try_load_header $path->load_path;
160     }
161    
162     $map or return;
163 root 1.8
164 root 1.9 $map->{reset_time} = $cf::RUNTIME + $map->reset_timeout;
165 root 1.8 $map->instantiate;
166    
167     # per-player maps become, after loading, normal maps
168     $map->per_player (0) if $path->{user_rel};
169 root 1.1 }
170    
171     $map->path ($key);
172     $map->{path} = $path;
173    
174     $map->reset if $map->should_reset;
175    
176     $cf::MAP{$key} = $map
177     }
178     }
179    
180 root 1.8 sub cf::map::load {
181 root 1.1 my ($self) = @_;
182    
183 root 1.8 return if $self->in_memory != cf::MAP_SWAPPED;
184 root 1.1
185     $self->in_memory (cf::MAP_LOADING);
186    
187     my $path = $self->{path};
188    
189     $self->alloc;
190     $self->load_objects ($self->{load_path}, 1)
191     or return;
192    
193     if (my $uniq = $path->uniq_path) {
194     utf8::encode $uniq;
195     if (aio_open $uniq, O_RDONLY, 0) {
196     $self->clear_unique_items;
197     $self->load_objects ($uniq, 0);
198     }
199     }
200    
201     # now do the right thing for maps
202     $self->link_multipart_objects;
203 root 1.10
204     unless ($self->{path}->is_style_map) {
205     $self->fix_auto_apply;
206     $self->decay_objects;
207     $self->update_buttons;
208     $self->set_darkness_map;
209     $self->difficulty ($self->estimate_difficulty)
210     unless $self->difficulty;
211     $self->activate;
212     }
213 root 1.1
214     $self->in_memory (cf::MAP_IN_MEMORY);
215     }
216    
217 root 1.8 sub cf::map::load_map_sync {
218     my ($path, $origin) = @_;
219 root 1.1
220 root 1.8 warn "load_map_sync<$path, $origin>\n";#d#
221 root 1.1
222 root 1.9 cf::abort if $path =~ /CVS/;#d#
223    
224     cf::sync_job {
225 root 1.8 my $map = cf::map::find_map $path, $origin
226     or return;
227     $map->load;
228     $map
229     }
230 root 1.1 }
231    
232     sub cf::map::save {
233     my ($self) = @_;
234    
235     my $save = $self->{path}->save_path; utf8::encode $save;
236     my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
237    
238 root 1.9 $self->{last_save} = $cf::RUNTIME;
239    
240     return unless $self->dirty;
241    
242     $self->{load_path} = $save;
243    
244 root 1.10 return if $self->{path}->is_style_map;
245 root 1.9
246     warn "saving map ", $self->path;
247    
248 root 1.1 if ($uniq) {
249     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
250     $self->save_objects ($uniq, cf::IO_UNIQUES);
251     } else {
252     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
253     }
254     }
255    
256 root 1.2 sub cf::map::swap_out {
257     my ($self) = @_;
258    
259 root 1.6 return if $self->players;
260 root 1.8 return if $self->in_memory != cf::MAP_IN_MEMORY;
261 root 1.6
262 root 1.8 $self->save;
263 root 1.2 $self->clear;
264     $self->in_memory (cf::MAP_SWAPPED);
265     }
266    
267 root 1.1 sub cf::map::should_reset {
268     my ($map) = @_;
269    
270 root 1.9 return;#d#
271 root 1.1 # TODO: safety, remove and allow resettable per-player maps
272     return if $map->{path}{user_rel};#d#
273     return unless $map->reset_timeout;
274    
275 root 1.9 my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access;
276 root 1.1
277     $time + $map->reset_timeout < $cf::RUNTIME
278     }
279    
280     sub cf::map::reset {
281     my ($self) = @_;
282    
283     return if $self->players;
284 root 1.3 return if $self->{path}{user_rel};#d#
285 root 1.1
286     warn "resetting map ", $self->path;#d#
287 root 1.9 return;#d#
288 root 1.3
289     utf8::encode (my $save = $self->{path}->save_path);
290     aioreq_pri 3; IO::AIO::aio_unlink $save;
291     aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
292 root 1.1
293     $self->clear;
294     $self->in_memory (cf::MAP_SWAPPED);
295 root 1.3 utf8::encode ($self->{load_path} = $self->{path}->load_path);
296 root 1.1 }
297    
298     sub cf::object::player::enter_exit {
299     my ($ob, $exit) = @_;
300    
301 root 1.6 return unless $ob->type == cf::PLAYER;
302    
303     my ($oldmap, $oldx, $oldy) = ($ob->map, $ob->x, $ob->y);
304    
305 root 1.9 $ob->enter_map ($LINK_MAP, 20, 20);
306     $ob->deactivate_recursive;
307    
308     (Coro::async {
309 root 1.3 my ($map, $x, $y);
310 root 1.9 unless (eval {
311 root 1.1
312 root 1.9 prepare_random_map $exit
313     if $exit->slaying eq "/!";
314 root 1.1
315 root 1.9 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
316 root 1.8
317 root 1.9 $map = cf::map::find_map $path->as_string;
318     $map = $map->customise_for ($ob) if $map;
319     ($x, $y) = ($exit->stats->hp, $exit->stats->sp);
320 root 1.8
321 root 1.9 unless ($map) {
322     $ob->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
323 root 1.7
324 root 1.9 # restore original map position
325     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
326 root 1.7
327 root 1.9 unless ($map) {
328     $map = cf::map::find_map $emergency_position->[0]
329     or die "FATAL: cannot load emergency map\n";
330     $x = $emergency_position->[1];
331     $y = $emergency_position->[2];
332     }
333 root 1.7 }
334 root 1.1
335 root 1.9 # use -1, -1 as default coordinates, not 0, 0
336     ($x, $y) = ($map->enter_x, $map->enter_y)
337     if $x <=0 && $y <= 0;
338    
339     warn "entering ", $map->path, " at ($x, $y)\n";#d#
340     $map->load;
341     1;
342     }) {
343     ($map, $x, $y) = ($oldmap, $oldx, $oldy);
344    
345     $ob->message ("Something went wrong within the server. "
346     . "I'll try to bring you back to the map you were before. "
347     . "Please report this to the dungeon master",
348     cf::NDI_UNIQUE | cf::NDI_RED);
349 root 1.7
350 root 1.9 warn "ERROR in enter_exit: $@";
351     }
352     $ob->activate_recursive;
353 root 1.6 $ob->enter_map ($map, $x, $y);
354 root 1.9 })->prio (1);
355 root 1.1 }
356    
357     sub cf::map::customise_for {
358     my ($map, $ob) = @_;
359    
360     if ($map->per_player) {
361 root 1.8 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
362 root 1.1 }
363    
364     $map
365     }
366    
367     sub cf::map::emergency_save {
368     local $cf::FREEZE = 1;
369    
370 root 1.4 warn "enter emergency map save\n";
371    
372 root 1.9 cf::sync_job {
373 root 1.4 warn "begin emergency map save\n";
374     $_->save for values %cf::MAP;
375     };
376    
377 root 1.1 warn "end emergency map save\n";
378     }
379