… | |
… | |
2 | |
2 | |
3 | # additional support for cfplus client |
3 | # additional support for cfplus client |
4 | |
4 | |
5 | use NPC_Dialogue; |
5 | use NPC_Dialogue; |
6 | |
6 | |
|
|
7 | =head1 CF+ protocol extensions |
|
|
8 | |
|
|
9 | This module implements protocol extensions for use by the CF+ client, but |
|
|
10 | can be used by other clients as well. It uses the C<extcmd> mechanism |
|
|
11 | exclusively. |
|
|
12 | |
|
|
13 | =over 4 |
|
|
14 | |
|
|
15 | =item ... = extcmd cfplus_support { version => $client_version } |
|
|
16 | |
|
|
17 | Registers the client the the server. the client should send the highest |
|
|
18 | version of the protocol it supports itself, and the server returns the |
|
|
19 | highest version of the protocol it supports in the C<version> key itself. |
|
|
20 | |
|
|
21 | =cut |
|
|
22 | |
7 | cf::register_extcmd cfplus_support => sub { |
23 | cf::register_extcmd cfplus_support => sub { |
8 | my ($pl, $data) = @_; |
24 | my ($pl, $msg) = @_; |
9 | |
25 | |
10 | my ($token, $client_version) = split / /, $data, 2; |
26 | # $msg->{version} |
11 | |
27 | |
12 | $pl->send ("ext $token 1"); |
28 | (version => 2) |
13 | }; |
29 | }; |
14 | |
30 | |
15 | my %dialog; # currently active dialogs |
31 | my %dialog; # currently active dialogs |
16 | |
32 | |
|
|
33 | my $timer = Event->timer (interval => 0.2, parked => 1, cb => sub { |
|
|
34 | while (my ($id, $dialog) = each %dialog) { |
|
|
35 | my (undef, $dx, $dy) = $dialog->{ob}->rangevector ($dialog->{npc}); |
|
|
36 | next if (abs $dx) <= 2 && (abs $dy) <= 2; |
|
|
37 | |
|
|
38 | $dialog->{ob}->contr->ext_reply ($id => msgtype => "error", msg => "out of range"); |
|
|
39 | delete $dialog{$id}; |
|
|
40 | } |
|
|
41 | |
|
|
42 | $_[0]->w->stop unless keys %dialog; |
|
|
43 | }); |
|
|
44 | |
17 | sub dialog_tell { |
45 | sub dialog_tell { |
18 | my ($token, $dialog, $msg) = @_; |
46 | my ($id, $dialog, $msg) = @_; |
19 | |
47 | |
20 | my $pl = $dialog->{ob}->contr; |
48 | my $pl = $dialog->{ob}->contr; |
21 | my ($reply, @kw) = $dialog->tell ($msg); |
49 | my ($reply, @kw) = $dialog->tell ($msg); |
22 | $reply = "..." unless $reply; |
50 | $reply = "..." unless $reply; |
23 | $pl->send ("ext $token msg " . join "\x00", $reply, @kw); |
51 | |
|
|
52 | $pl->ext_reply ($id => msgtype => "reply", msg => $reply, add_topics => \@kw); |
24 | } |
53 | } |
25 | |
54 | |
|
|
55 | =item ... = extcmd lookat { dx => $dx, dy => $dy } |
|
|
56 | |
|
|
57 | "Looks at" the mapspace displaced (dx|dy) relative to the player |
26 | # return "interesting" information about the given tile |
58 | and returns "interesting" information about it. |
27 | # currently only returns the npc_dialog title when a dialog is possible |
59 | |
|
|
60 | Keys it can return include: |
|
|
61 | |
|
|
62 | npc_dialog => $name |
|
|
63 | There is an npc or other object that can "talk" to the player. |
|
|
64 | |
|
|
65 | =cut |
|
|
66 | |
28 | cf::register_extcmd lookat => sub { |
67 | cf::register_extcmd lookat => sub { |
29 | my ($pl, $data) = @_; |
68 | my ($pl, $msg) = @_; |
|
|
69 | my ($dx, $dy) = @$msg{qw(dx dy)}; |
30 | |
70 | |
31 | my ($token, $dx, $dy) = split / /, $data; |
|
|
32 | my $near = (abs $dx) <= 2 && (abs $dy) <= 2; |
71 | my $near = (abs $dx) <= 2 && (abs $dy) <= 2; |
33 | |
72 | |
34 | my %res; |
73 | my %res; |
35 | |
74 | |
36 | if ($pl->cell_visible ($dx, $dy)) { |
75 | if ($pl->cell_visible ($dx, $dy)) { |
… | |
… | |
38 | $res{npc_dialog} = $ob->name |
77 | $res{npc_dialog} = $ob->name |
39 | if $near && NPC_Dialogue::has_dialogue $ob; |
78 | if $near && NPC_Dialogue::has_dialogue $ob; |
40 | } |
79 | } |
41 | } |
80 | } |
42 | |
81 | |
43 | $pl->send ("ext $token " . join "\x00", %res); |
82 | %res |
44 | }; |
83 | }; |
|
|
84 | |
|
|
85 | =item ... = extcmd npc_dialog_begin { msgid => $id, dx => $dx, dy => $dy } |
|
|
86 | |
|
|
87 | Tries to start a dialogue with the mapspace specified by $dx and $dy (see |
|
|
88 | C<extcmd lookat>). The $msgid will be used as a handle for all future |
|
|
89 | messages related to this dialog interaction. |
|
|
90 | |
|
|
91 | It either replies with an error reply or starts a dialog by telling |
|
|
92 | the npc "hi" and returning a reply strcuture as with C<extcmd |
|
|
93 | npc_dialog_tell>. |
|
|
94 | |
|
|
95 | =cut |
45 | |
96 | |
46 | cf::register_extcmd npc_dialog_begin => sub { |
97 | cf::register_extcmd npc_dialog_begin => sub { |
47 | my ($pl, $data) = @_; |
98 | my ($pl, $msg) = @_; |
48 | |
99 | my ($id, $dx, $dy) = @$msg{qw(msgid dx dy)}; |
49 | my ($token, $dx, $dy) = split / /, $data; |
|
|
50 | |
100 | |
51 | return unless (abs $dx) <= 2 && (abs $dy) <= 2; |
101 | return unless (abs $dx) <= 2 && (abs $dy) <= 2; |
52 | return unless $pl->cell_visible ($dx, $dy); |
102 | return unless $pl->cell_visible ($dx, $dy); |
53 | |
103 | |
54 | for my $npc ($pl->ob->map->at ($pl->ob->x + $dx, $pl->ob->y + $dy)) { |
104 | for my $npc ($pl->ob->map->at ($pl->ob->x + $dx, $pl->ob->y + $dy)) { |
55 | if (NPC_Dialogue::has_dialogue $npc) { |
105 | if (NPC_Dialogue::has_dialogue $npc) { |
56 | $dialog{$token} = new NPC_Dialogue ob => $pl->ob, npc => $npc; |
106 | $dialog{$id} = new NPC_Dialogue ob => $pl->ob, npc => $npc; |
57 | dialog_tell $token, $dialog{$token}, "hi"; |
107 | dialog_tell $id, $dialog{$id}, "hi"; |
|
|
108 | $timer->start; |
58 | return; |
109 | return; |
59 | } |
110 | } |
60 | } |
111 | } |
61 | |
112 | |
62 | $pl->send ("ext $token error"); |
113 | (msgtype => "error", msg => "nothing to talk to found") |
63 | }; |
114 | }; |
|
|
115 | |
|
|
116 | =item ... = extcmd npc_dialog_tell { msgid => $id, msg => $text } |
|
|
117 | |
|
|
118 | Tells the NPC the given $text message and returns a reply structure which |
|
|
119 | can have the following keys: |
|
|
120 | |
|
|
121 | msgtype => "reply" |
|
|
122 | msg => $reply_text, |
|
|
123 | add_topics => [additional topic strings] |
|
|
124 | del_topics => [invalidated topic strings] |
|
|
125 | |
|
|
126 | =cut |
64 | |
127 | |
65 | cf::register_extcmd npc_dialog_tell => sub { |
128 | cf::register_extcmd npc_dialog_tell => sub { |
66 | my ($pl, $data) = @_; |
129 | my ($pl, $msg) = @_; |
67 | |
130 | |
68 | my ($token, $msg) = split / /, $data, 2; |
131 | dialog_tell $msg->{msgid}, $dialog{$msg->{msgid}}, $msg->{msg} |
|
|
132 | if $dialog{$msg->{msgid}}; |
69 | |
133 | |
70 | dialog_tell $token, $dialog{$token}, $msg |
134 | () |
71 | if $dialog{$token}; |
|
|
72 | }; |
135 | }; |
|
|
136 | |
|
|
137 | =item extcmd npc_dialog_end { msgid => $id } |
|
|
138 | |
|
|
139 | Finishes the dialog, invalidating the handle. |
|
|
140 | |
|
|
141 | =cut |
73 | |
142 | |
74 | cf::register_extcmd npc_dialog_end => sub { |
143 | cf::register_extcmd npc_dialog_end => sub { |
75 | my ($pl, $token) = @_; |
|
|
76 | |
|
|
77 | delete $dialog{$token}; |
|
|
78 | }; |
|
|
79 | |
|
|
80 | sub on_logout { |
|
|
81 | my ($pl, $host) = @_; |
144 | my ($pl, $msg) = @_; |
82 | |
145 | |
|
|
146 | delete $dialog{$msg->{msgid}}; |
|
|
147 | |
|
|
148 | () |
|
|
149 | }; |
|
|
150 | |
|
|
151 | cf::attach_to_players |
|
|
152 | on_logout => sub { |
|
|
153 | my ($pl) = @_; |
|
|
154 | |
83 | delete $dialog{$_} for grep $pl->ob == $dialog{$_}{ob}, keys %dialog; |
155 | delete $dialog{$_} for grep $pl->ob == $dialog{$_}{ob}, keys %dialog; |
|
|
156 | }, |
|
|
157 | ; |
84 | |
158 | |
|
|
159 | =item ... = extcmd editor_support |
|
|
160 | |
|
|
161 | Returns the value required by clients that have an editor to download and |
|
|
162 | upload maps from/to the server. |
|
|
163 | |
|
|
164 | cvs_root => the (http) url where the cvs root for downloading is located |
|
|
165 | upload => the (http) url where clients can upload maps |
|
|
166 | server => the hostname:port of the test server the maps can be tested on |
|
|
167 | |
|
|
168 | If those values are not supplied or empty strings, the server does not |
|
|
169 | support downloading, uploading, testing, respectively. |
|
|
170 | |
|
|
171 | The upload script expects the following values in a multipart form upload: |
|
|
172 | |
|
|
173 | client: a descriptive string describing the editor and version used to upload |
|
|
174 | path: absolute server-side map path beginning with / |
|
|
175 | map: the map file itself |
|
|
176 | mapdir: the cvs root url originally used to download the map |
|
|
177 | revision: cvs-revision originally used to download the map |
|
|
178 | comment: a comment supplied by the user that documents the changes |
|
|
179 | cf_login: crossfire server login |
|
|
180 | cf_password: crossfire server password, optionally used for authentication purposes |
|
|
181 | |
|
|
182 | =cut |
|
|
183 | |
|
|
184 | cf::register_extcmd editor_support => sub { |
|
|
185 | my ($pl, $msg) = @_; |
|
|
186 | |
85 | 0 |
187 | ( |
|
|
188 | cvs_root => $cf::CFG{editor_cvs_root}, |
|
|
189 | upload => $cf::CFG{editor_upload}, |
|
|
190 | server => $cf::CFG{editor_server}, |
|
|
191 | ) |
|
|
192 | }; |
|
|
193 | |
|
|
194 | sub unload { |
|
|
195 | while (my ($id, $dialog) = each %dialog) { |
|
|
196 | $dialog->{ob}->contr->ext_reply ($id => msgtype => "error", msg => "npc dialogue module was reloaded"); |
|
|
197 | } |
|
|
198 | |
|
|
199 | %dialog = (); |
86 | } |
200 | } |
87 | |
201 | |
88 | sub on_clock { |
202 | =back |
89 | return 0 unless %dialog; |
|
|
90 | |
203 | |
91 | while (my ($token, $dialog) = each %dialog) { |
204 | =cut |
92 | my (undef, $dx, $dy) = $dialog->{ob}->rangevector ($dialog->{npc}); |
|
|
93 | next if (abs $dx) <= 2 && (abs $dy) <= 2; |
|
|
94 | |
205 | |
95 | $dialog->{ob}->contr->send ("ext $token out_of_range"); |
|
|
96 | delete $dialog{$token}; |
|
|
97 | } |
|
|
98 | |
|
|
99 | 0 |
|
|
100 | } |
|
|
101 | |
|
|