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 | |
5 | use Digest::MD5; |
|
|
6 | use Fcntl; |
5 | use Fcntl; |
7 | |
6 | |
8 | use Coro; |
7 | use Coro; |
9 | use Coro::AIO; |
8 | use Coro::AIO; |
10 | |
9 | |
… | |
… | |
15 | our $SCHEDULE_INTERVAL = 8; # time the map scheduler sleeps between runs |
14 | our $SCHEDULE_INTERVAL = 8; # time the map scheduler sleeps between runs |
16 | our $SAVE_TIMEOUT = 60; # save maps every n seconds |
15 | our $SAVE_TIMEOUT = 60; # save maps every n seconds |
17 | our $SAVE_INTERVAL = 0.4; # save at max. one map every $SAVE_HOLD |
16 | our $SAVE_INTERVAL = 0.4; # save at max. one map every $SAVE_HOLD |
18 | our $MAX_RESET = 7200; |
17 | our $MAX_RESET = 7200; |
19 | |
18 | |
20 | our $RANDOM_MAPS = cf::localdir . "/random"; |
|
|
21 | mkdir $RANDOM_MAPS; |
|
|
22 | |
|
|
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 | |
|
|
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 | |
|
|
141 | sub 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 | |
22 | |
170 | our $SCHEDULER = cf::coro { |
23 | our $SCHEDULER = cf::coro { |
171 | Coro::Timer::sleep 3600;#d#TODO#for debugging only |
24 | Coro::Timer::sleep 3600;#d#TODO#for debugging only |
172 | while () { |
25 | while () { |
173 | Coro::Timer::sleep $SCHEDULE_INTERVAL; |
26 | Coro::Timer::sleep $SCHEDULE_INTERVAL; |
174 | |
|
|
175 | write_runtime |
|
|
176 | or warn "unable to write runtime file: $!"; |
|
|
177 | |
27 | |
178 | for my $map (values %cf::MAP) { |
28 | for my $map (values %cf::MAP) { |
179 | eval { |
29 | eval { |
180 | next if $map->in_memory != cf::MAP_IN_MEMORY; |
30 | next if $map->in_memory != cf::MAP_IN_MEMORY; |
181 | next if $map->players; |
31 | next if $map->players; |
… | |
… | |
204 | $SCHEDULER->prio (-2); |
54 | $SCHEDULER->prio (-2); |
205 | |
55 | |
206 | sub generate_random_map { |
56 | sub generate_random_map { |
207 | my ($path, $rmp) = @_; |
57 | my ($path, $rmp) = @_; |
208 | |
58 | |
209 | # mit "rum" bekleckert, nicht |
59 | # mit "rum" bekleckern, nicht |
210 | cf::map::_create_random_map |
60 | cf::map::_create_random_map |
211 | $path, |
61 | $path, |
212 | $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, |
62 | $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, |
213 | $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, |
63 | $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, |
214 | $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map}, |
64 | $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map}, |
… | |
… | |
217 | $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3}, |
67 | $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3}, |
218 | $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase}, |
68 | $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase}, |
219 | $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation}, |
69 | $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation}, |
220 | $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp}, |
70 | $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp}, |
221 | $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used}, |
71 | $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used}, |
222 | $rmp->{region} |
72 | (cf::region::find $rmp->{region}) |
223 | } |
73 | } |
224 | |
74 | |
225 | sub parse_random_map_params { |
75 | sub parse_random_map_params { |
226 | my ($spec) = @_; |
76 | my ($spec) = @_; |
227 | |
77 | |
… | |
… | |
247 | # that depends on the exit object |
97 | # that depends on the exit object |
248 | |
98 | |
249 | my $rmp = parse_random_map_params $exit->msg; |
99 | my $rmp = parse_random_map_params $exit->msg; |
250 | |
100 | |
251 | if ($exit->map) { |
101 | if ($exit->map) { |
252 | $rmp->{region} = $exit->map->region ? $exit->map->region->name : undef; |
102 | $rmp->{region} = $exit->map->region_name; |
253 | $rmp->{origin_map} = $exit->map->path; |
103 | $rmp->{origin_map} = $exit->map->path; |
254 | $rmp->{origin_x} = $exit->x; |
104 | $rmp->{origin_x} = $exit->x; |
255 | $rmp->{origin_y} = $exit->y; |
105 | $rmp->{origin_y} = $exit->y; |
256 | } |
106 | } |
257 | |
107 | |