ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/00_map_handling.ext
(Generate patch)

Comparing deliantra/server/ext/00_map_handling.ext (file contents):
Revision 1.10 by root, Sun Dec 31 18:10:40 2006 UTC vs.
Revision 1.14 by root, Mon Jan 1 00:41:03 2007 UTC

1#! perl 1#! perl
2 2
3# why this is an extension is a good question. next question. 3# why this is an extension is a good question. next question.
4 4
5use Digest::MD5;
6use Fcntl; 5use Fcntl;
7 6
8use Coro; 7use Coro;
9use Coro::AIO; 8use Coro::AIO;
10 9
11our $emergency_position = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37]; 10our $emergency_position = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37];
12 11
13our $DEACTIVATE_TIMEOUT = 60; # number of seconds after which maps get deactivated to save cpu 12our $DEACTIVATE_TIMEOUT = 60; # number of seconds after which maps get deactivated to save cpu
14our $SWAP_TIMEOUT = 600; # number of seconds after which maps inactive get swapped out 13our $SWAP_TIMEOUT = 600; # number of seconds after which maps inactive get swapped out
15our $SCHEDULE_INTERVAL = 8; # time the map scheduler sleeps between runs 14our $SCHEDULE_INTERVAL = 8; # time the map scheduler sleeps between runs
16our $SAVE_TIMEOUT = 60; # save maps every n seconds 15our $SAVE_TIMEOUT = 60; # save maps every n seconds
17our $SAVE_INTERVAL = 0.4; # save at max. one map every $SAVE_HOLD 16our $SAVE_INTERVAL = 0.4; # save at max. one map every $SAVE_HOLD
18our $MAX_RESET = 7200; 17our $MAX_RESET = 7200;
19
20our $RANDOM_MAPS = cf::localdir . "/random";
21mkdir $RANDOM_MAPS;
22 18
23$DEACTIVATE_TIMEOUT = 3;#d# 19$DEACTIVATE_TIMEOUT = 3;#d#
24$SWAP_TIMEOUT = 5;#d# 20$SWAP_TIMEOUT = 5;#d#
25$SCHEDULE_INTERVAL = 1; 21$SCHEDULE_INTERVAL = 1;
26 22
27$cf::LINK_MAP ||= do {
28 my $map = cf::map::new;
29
30 $map->width (41);
31 $map->height (41);
32 $map->alloc;
33 $map->path ("{link}");
34 $map->{path} = bless { path => "{link}" }, "cf::path";
35 $map->in_memory (cf::MAP_IN_MEMORY);
36
37 $map
38};
39
40{
41 package cf::path;
42
43 sub new {
44 my ($class, $path, $base) = @_;
45
46 my $self = bless { }, $class;
47
48 if ($path =~ s{^\?random/}{}) {
49 $self->{random} = cf::from_json $path;
50 } else {
51 if ($path =~ s{^~([^/]+)?}{}) {
52 $self->{user_rel} = 1;
53
54 if (defined $1) {
55 $self->{user} = $1;
56 } elsif ($base =~ m{^~([^/]+)/}) {
57 $self->{user} = $1;
58 } else {
59 warn "cannot resolve user-relative path without user <$path,$base>\n";
60 }
61 } elsif ($path =~ /^\//) {
62 # already absolute
63 } else {
64 $base =~ s{[^/]+/?$}{};
65 return $class->new ("$base/$path");
66 }
67
68 for ($path) {
69 redo if s{/\.?/}{/};
70 redo if s{/[^/]+/\.\./}{/};
71 }
72 }
73
74 $self->{path} = $path;
75
76 $self
77 }
78
79 # the name / primary key / in-game path
80 sub as_string {
81 my ($self) = @_;
82
83 $self->{user_rel} ? "~$self->{user}$self->{path}"
84 : $self->{random} ? "?random/$self->{path}"
85 : $self->{path}
86 }
87
88 # the displayed name, this is a one way mapping
89 sub visible_name {
90 my ($self) = @_;
91
92 $self->{random} ? "?random/$self->{random}{origin_map}+$self->{random}{origin_x}+$self->{random}{origin_y}/$self->{random}{dungeon_level}"
93 : $self->as_string
94 }
95
96 # escape the /'s in the path
97 sub _escaped_path {
98 # ∕ is U+2215
99 (my $path = $_[0]{path}) =~ s/\//∕/g;
100 $path
101 }
102
103 # the original (read-only) location
104 sub load_path {
105 my ($self) = @_;
106
107 sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
108 }
109
110 # the temporary/swap location
111 sub save_path {
112 my ($self) = @_;
113
114 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
115 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, Digest::MD5::md5_hex $self->{path}
116 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
117 }
118
119 # the unique path, might be eq to save_path
120 sub uniq_path {
121 my ($self) = @_;
122
123 $self->{user_rel} || $self->{random}
124 ? undef
125 : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
126 }
127
128 # return random map parameters, or undef
129 sub random_map_params {
130 my ($self) = @_;
131
132 $self->{random}
133 }
134
135 # this is somewhat ugly, but style maps do need special treatment
136 sub is_style_map {
137 $_[0]{path} =~ m{^/styles/}
138 }
139}
140
141sub write_runtime {
142 my $runtime = cf::localdir . "/runtime";
143
144 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
145 or return;
146
147 my $value = $cf::RUNTIME;
148 (aio_write $fh, 0, (length $value), $value, 0) <= 0
149 and return;
150
151 aio_fsync $fh
152 and return;
153
154 close $fh
155 or return;
156
157 aio_rename "$runtime~", $runtime
158 and return;
159
160 1
161}
162
163(Coro::async {
164 unless (write_runtime) {
165 warn "unable to write runtime file: $!";
166 exit 1;
167 }
168})->prio (Coro::PRIO_MAX);
169
170our $SCHEDULER = cf::coro { 23our $SCHEDULER = cf::coro {
171 Coro::Timer::sleep 3600;#d#TODO#for debugging only
172 while () { 24 while () {
173 Coro::Timer::sleep $SCHEDULE_INTERVAL; 25 Coro::Timer::sleep $SCHEDULE_INTERVAL;
174
175 write_runtime
176 or warn "unable to write runtime file: $!";
177 26
178 for my $map (values %cf::MAP) { 27 for my $map (values %cf::MAP) {
179 eval { 28 eval {
180 next if $map->in_memory != cf::MAP_IN_MEMORY; 29 next if $map->in_memory != cf::MAP_IN_MEMORY;
181 next if $map->players; 30 next if $map->players;
204$SCHEDULER->prio (-2); 53$SCHEDULER->prio (-2);
205 54
206sub generate_random_map { 55sub generate_random_map {
207 my ($path, $rmp) = @_; 56 my ($path, $rmp) = @_;
208 57
209 # mit "rum" bekleckert, nicht 58 # mit "rum" bekleckern, nicht
210 cf::map::_create_random_map 59 cf::map::_create_random_map
211 $path, 60 $path,
212 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, 61 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
213 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, 62 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
214 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map}, 63 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
217 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3}, 66 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
218 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase}, 67 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
219 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation}, 68 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
220 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp}, 69 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
221 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used}, 70 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
222 $rmp->{region} 71 (cf::region::find $rmp->{region})
223} 72}
224 73
225sub parse_random_map_params { 74sub parse_random_map_params {
226 my ($spec) = @_; 75 my ($spec) = @_;
227 76
240} 89}
241 90
242sub prepare_random_map { 91sub prepare_random_map {
243 my ($exit) = @_; 92 my ($exit) = @_;
244 93
245 # all this does is basically rpelace the /! path by 94 # all this does is basically replace the /! path by
246 # a new random map path (?random/...) with a seed 95 # a new random map path (?random/...) with a seed
247 # that depends on the exit object 96 # that depends on the exit object
248 97
249 my $rmp = parse_random_map_params $exit->msg; 98 my $rmp = parse_random_map_params $exit->msg;
250 99
251 if ($exit->map) { 100 if ($exit->map) {
252 $rmp->{region} = $exit->map->region ? $exit->map->region->name : undef; 101 $rmp->{region} = $exit->map->region_name;
253 $rmp->{origin_map} = $exit->map->path; 102 $rmp->{origin_map} = $exit->map->path;
254 $rmp->{origin_x} = $exit->x; 103 $rmp->{origin_x} = $exit->x;
255 $rmp->{origin_y} = $exit->y; 104 $rmp->{origin_y} = $exit->y;
256 } 105 }
257 106
258 $rmp->{random_seed} = $exit->random_seed; 107 $rmp->{random_seed} ||= $exit->random_seed;
259 108
260 $exit->slaying ("?random/" . cf::to_json $rmp); 109 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");
261 $exit->msg (undef); 116 $exit->msg (undef);
117 }
262} 118}
263 119
264# and all this just because we cannot iterate over 120# and all this just because we cannot iterate over
265# all maps in C++... 121# all maps in C++...
266sub cf::map::change_all_map_light { 122sub cf::map::change_all_map_light {
280 or return; 136 or return;
281 137
282 $map->load_header ($path) 138 $map->load_header ($path)
283 or return; 139 or return;
284 140
285 $map->reset_time (0) if $map->reset_time > $cf::RUNTIME;
286 $map->reset_timeout (10);#d#
287
288 $map->{load_path} = $path; 141 $map->{load_path} = $path;
142 use Data::Dumper; warn Dumper $map;#d#
289 143
290 $map 144 $map
291} 145}
292 146
293sub cf::map::find_map { 147sub cf::map::find_map {
294 my ($path, $origin) = @_; 148 my ($path, $origin) = @_;
295 149
296 warn "find_map<$path,$origin>\n";#d# 150 #warn "find_map<$path,$origin>\n";#d#
297 151
298 $path = ref $path ? $path : new cf::path $path, $origin && $origin->path; 152 $path = ref $path ? $path : new cf::path $path, $origin && $origin->path;
299 my $key = $path->as_string; 153 my $key = $path->as_string;
300 154
301 $cf::MAP{$key} || do { 155 $cf::MAP{$key} || do {
302 # do it the slow way 156 # do it the slow way
303 my $map = try_load_header $path->save_path; 157 my $map = try_load_header $path->save_path;
304 158
305 if (!$map) { 159 if ($map) {
160 # safety
161 $map->{reset_time} = $cf::RUNTIME + $MAX_RESET
162 if $map->{reset_time} > $cf::RUNTIME + $MAX_RESET;
163 } else {
306 if (my $rmp = $path->random_map_params) { 164 if (my $rmp = $path->random_map_params) {
307 $map = generate_random_map $key, $rmp; 165 $map = generate_random_map $key, $rmp;
308 } else { 166 } else {
309 $map = try_load_header $path->load_path; 167 $map = try_load_header $path->load_path;
310 } 168 }
311 169
312 $map or return; 170 $map or return;
313 171
314 $map->{reset_time} = $cf::RUNTIME + $map->reset_timeout; 172 $map->{reset_time} = $cf::RUNTIME + ($map->reset_timeout || 3600);
315 $map->instantiate; 173 $map->instantiate;
316 174
317 # per-player maps become, after loading, normal maps 175 # per-player maps become, after loading, normal maps
318 $map->per_player (0) if $path->{user_rel}; 176 $map->per_player (0) if $path->{user_rel};
319 } 177 }
320 178
321 $map->path ($key); 179 $map->path ($key);
322 $map->{path} = $path; 180 $map->{path} = $path;
181 $map->last_access ($cf::RUNTIME);
323 182
324 $map->reset if $map->should_reset; 183 $map->reset if $map->should_reset;
325 184
326 $cf::MAP{$key} = $map 185 $cf::MAP{$key} = $map
327 } 186 }
365} 224}
366 225
367sub cf::map::load_map_sync { 226sub cf::map::load_map_sync {
368 my ($path, $origin) = @_; 227 my ($path, $origin) = @_;
369 228
370 warn "load_map_sync<$path, $origin>\n";#d# 229 #warn "load_map_sync<$path, $origin>\n";#d#
371
372 cf::abort if $path =~ /CVS/;#d#
373 230
374 cf::sync_job { 231 cf::sync_job {
375 my $map = cf::map::find_map $path, $origin 232 my $map = cf::map::find_map $path, $origin
376 or return; 233 or return;
377 $map->load; 234 $map->load;
415} 272}
416 273
417sub cf::map::should_reset { 274sub cf::map::should_reset {
418 my ($map) = @_; 275 my ($map) = @_;
419 276
420 return;#d#
421 # TODO: safety, remove and allow resettable per-player maps 277 # TODO: safety, remove and allow resettable per-player maps
422 return if $map->{path}{user_rel};#d# 278 return if $map->{path}{user_rel};#d#
423 return unless $map->reset_timeout; 279 #return unless $map->reset_timeout;
424 280
425 my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access; 281 my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access;
426 282
427 $time + $map->reset_timeout < $cf::RUNTIME 283 $time < $cf::RUNTIME
428} 284}
429 285
430sub cf::map::reset { 286sub cf::map::reset {
431 my ($self) = @_; 287 my ($self) = @_;
432 288
490 $map->load; 346 $map->load;
491 1; 347 1;
492 }) { 348 }) {
493 ($map, $x, $y) = ($oldmap, $oldx, $oldy); 349 ($map, $x, $y) = ($oldmap, $oldx, $oldy);
494 350
495 $ob->message ("Something went wrong within the server. " 351 $ob->message ("Something went wrong deep within the crossfire server. "
496 . "I'll try to bring you back to the map you were before. " 352 . "I'll try to bring you back to the map you were before. "
497 . "Please report this to the dungeon master", 353 . "Please report this to the dungeon master",
498 cf::NDI_UNIQUE | cf::NDI_RED); 354 cf::NDI_UNIQUE | cf::NDI_RED);
499 355
500 warn "ERROR in enter_exit: $@"; 356 warn "ERROR in enter_exit: $@";

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines