Fix WebTiles compilation on OS X
[crawl.git] / crawl-ref / source / tileweb.cc
1 #include "AppHdr.h"
2
3 #ifdef USE_TILE_WEB
4
5 #include "tileweb.h"
6
7 #include <errno.h>
8 #include <stdarg.h>
9 #include <sys/socket.h>
10 #include <sys/time.h>
11 #include <sys/types.h>
12 #include <sys/un.h>
13 #include <unistd.h>
14
15 #include "artefact.h"
16 #include "branch.h"
17 #include "coord.h"
18 #include "directn.h"
19 #include "english.h"
20 #include "env.h"
21 #include "files.h"
22 #include "itemname.h"
23 #include "json.h"
24 #include "lang-fake.h"
25 #include "libutil.h"
26 #include "map_knowledge.h"
27 #include "menu.h"
28 #include "message.h"
29 #include "mon-util.h"
30 #include "notes.h"
31 #include "options.h"
32 #include "player.h"
33 #include "religion.h"
34 #include "skills.h"
35 #include "state.h"
36 #include "stringutil.h"
37 #include "tiledef-dngn.h"
38 #include "tiledef-gui.h"
39 #include "tiledef-icons.h"
40 #include "tiledef-main.h"
41 #include "tiledef-player.h"
42 #include "tilemcache.h"
43 #include "tilepick.h"
44 #include "tilepick-p.h"
45 #include "tileview.h"
46 #include "travel.h"
47 #include "unicode.h"
48 #include "unwind.h"
49 #include "version.h"
50 #include "viewgeom.h"
51 #include "view.h"
52
53 static unsigned int get_milliseconds()
54 {
55     // This is Unix-only, but so is Webtiles at the moment.
56     timeval tv;
57     gettimeofday(&tv, NULL);
58
59     return ((unsigned int) tv.tv_sec) * 1000 + tv.tv_usec / 1000;
60 }
61
62 // Helper for json.h
63 struct JsonWrapper
64 {
65     JsonWrapper(JsonNode* n) : node(n)
66     { }
67
68     ~JsonWrapper()
69     {
70         if (node)
71             json_delete(node);
72     }
73
74     JsonNode* operator->()
75     {
76         return node;
77     }
78
79     void check(JsonTag tag)
80     {
81         if (!node || node->tag != tag)
82             throw malformed;
83     }
84
85     JsonNode* node;
86
87     static class MalformedException { } malformed;
88 };
89
90 TilesFramework tiles;
91
92 TilesFramework::TilesFramework()
93     : m_crt_mode(CRT_NORMAL),
94       m_controlled_from_web(false),
95       m_last_ui_state(UI_INIT),
96       m_view_loaded(false),
97       m_next_view_tl(0, 0),
98       m_next_view_br(-1, -1),
99       m_current_flash_colour(BLACK),
100       m_next_flash_colour(BLACK),
101       m_need_full_map(true),
102       m_text_crt("crt"),
103       m_text_menu("menu_txt"),
104       m_print_fg(15)
105 {
106     screen_cell_t default_cell;
107     default_cell.tile.bg = TILE_FLAG_UNSEEN;
108     m_next_view.init(default_cell);
109     m_current_view.init(default_cell);
110 }
111
112 TilesFramework::~TilesFramework()
113 {
114 }
115
116 void TilesFramework::shutdown()
117 {
118     close(m_sock);
119     remove(m_sock_name.c_str());
120 }
121
122 void TilesFramework::draw_doll_edit()
123 {
124 }
125
126 bool TilesFramework::initialise()
127 {
128     // Init socket
129     m_sock = socket(PF_UNIX, SOCK_DGRAM, 0);
130     if (m_sock < 0)
131         die("Can't open the webtiles socket!");
132     sockaddr_un addr;
133     addr.sun_family = AF_UNIX;
134     strcpy(addr.sun_path, m_sock_name.c_str());
135     if (::bind(m_sock, (sockaddr*) &addr, sizeof(sockaddr_un)))
136         die("Can't bind the webtiles socket!");
137
138     int bufsize = 64 * 1024;
139     if (setsockopt(m_sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)))
140         die("Can't set buffer size!");
141     // Need small maximum message size to avoid crashes in OS X
142     m_max_msg_size = 2048;
143
144     struct timeval tv;
145     tv.tv_sec = 1;
146     tv.tv_usec = 0;
147     if (setsockopt(m_sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
148         die("Can't set send timeout!");
149
150     if (m_await_connection)
151         _await_connection();
152
153     _send_version();
154     send_exit_reason("unknown");
155     _send_options();
156
157     m_cursor[CURSOR_MOUSE] = NO_CURSOR;
158     m_cursor[CURSOR_TUTORIAL] = NO_CURSOR;
159     m_cursor[CURSOR_MAP] = NO_CURSOR;
160
161     // Initially, switch to CRT.
162     cgotoxy(1, 1, GOTO_CRT);
163
164     return true;
165 }
166
167 string TilesFramework::get_message()
168 {
169     return m_msg_buf;
170 }
171
172 void TilesFramework::write_message(const char *format, ...)
173 {
174     char buf[2048];
175     int len;
176
177     va_list argp;
178     va_start(argp, format);
179     if ((len = vsnprintf(buf, sizeof(buf), format, argp)) < 0)
180         die("Webtiles message format error! (%s)", format);
181     else if (len >= (int)sizeof(buf))
182         die("Webtiles message too long! (%d)", len);
183     va_end(argp);
184
185     m_msg_buf.append(buf);
186 }
187
188 void TilesFramework::finish_message()
189 {
190     if (m_msg_buf.size() == 0)
191         return;
192
193     m_msg_buf.append("\n");
194     const char* fragment_start = m_msg_buf.data();
195     const char* data_end = m_msg_buf.data() + m_msg_buf.size();
196     while (fragment_start < data_end)
197     {
198         int fragment_size = data_end - fragment_start;
199         if (fragment_size > m_max_msg_size)
200             fragment_size = m_max_msg_size;
201
202         for (unsigned int i = 0; i < m_dest_addrs.size(); ++i)
203         {
204             int retries = 30;
205             ssize_t sent = 0;
206             while (sent < fragment_size)
207             {
208                 ssize_t retval = sendto(m_sock, fragment_start + sent,
209                     fragment_size - sent, 0, (sockaddr*) &m_dest_addrs[i],
210                     sizeof(sockaddr_un));
211                 if (retval <= 0)
212                 {
213                     const char *errmsg = retval == 0 ? "No bytes sent"
214                                                      : strerror(errno);
215                     if (--retries <= 0)
216                         die("Socket write error: %s", errmsg);
217
218                     if (retval == 0 || errno == ENOBUFS || errno == EWOULDBLOCK
219                         || errno == EINTR || errno == EAGAIN)
220                     {
221                         // Wait for half a second at first (up to five), then
222                         // try again.
223                         usleep(retries <= 10 ? 5000 * 1000 : 500 * 1000);
224                     }
225                     else if (errno == ECONNREFUSED || errno == ENOENT)
226                     {
227                         // the other side is dead
228                         m_dest_addrs.erase(m_dest_addrs.begin() + i);
229                         i--;
230                         break;
231                     }
232                     else
233                         die("Socket write error: %s", errmsg);
234                 }
235                 else
236                     sent += retval;
237             }
238         }
239
240         fragment_start += fragment_size;
241     }
242     m_msg_buf.clear();
243 }
244
245 void TilesFramework::send_message(const char *format, ...)
246 {
247     char buf[2048];
248     int len;
249
250     va_list argp;
251     va_start(argp, format);
252     if ((len = vsnprintf(buf, sizeof(buf), format, argp)) >= (int)sizeof(buf)
253         || len == -1)
254     {
255         if (len == -1)
256             die("Webtiles message format error! (%s)", format);
257         else
258             die("Webtiles message too long! (%d)", len);
259     }
260     va_end(argp);
261
262     m_msg_buf.append(buf);
263
264     finish_message();
265 }
266
267 void TilesFramework::flush_messages()
268 {
269     send_message("*{\"msg\":\"flush_messages\"}");
270 }
271
272 void TilesFramework::_await_connection()
273 {
274     while (m_dest_addrs.size() == 0)
275         _receive_control_message();
276 }
277
278 wint_t TilesFramework::_receive_control_message()
279 {
280     char buf[4096]; // Should be enough for client->server messages
281     sockaddr_un srcaddr;
282     socklen_t srcaddr_len;
283
284     srcaddr_len = sizeof(srcaddr);
285
286     int len = recvfrom(m_sock, buf, sizeof(buf),
287                        0,
288                        (sockaddr *) &srcaddr, &srcaddr_len);
289
290     if (len == -1)
291         die("Socket read error: %s", strerror(errno));
292
293     string data(buf, len);
294     try
295     {
296         return _handle_control_message(srcaddr, data);
297     }
298     catch (JsonWrapper::MalformedException&)
299     {
300         dprf("Malformed control message!");
301         return 0;
302     }
303 }
304
305 wint_t TilesFramework::_handle_control_message(sockaddr_un addr, string data)
306 {
307     JsonWrapper obj = json_decode(data.c_str());
308     obj.check(JSON_OBJECT);
309
310     JsonWrapper msg = json_find_member(obj.node, "msg");
311     msg.check(JSON_STRING);
312     string msgtype(msg->string_);
313
314     int c = 0;
315
316     if (msgtype == "attach")
317     {
318         JsonWrapper primary = json_find_member(obj.node, "primary");
319         primary.check(JSON_BOOL);
320
321         m_dest_addrs.push_back(addr);
322         m_controlled_from_web = primary->bool_;
323     }
324     else if (msgtype == "key")
325     {
326         JsonWrapper keycode = json_find_member(obj.node, "keycode");
327         keycode.check(JSON_NUMBER);
328
329         c = (int) keycode->number_;
330     }
331     else if (msgtype == "spectator_joined")
332     {
333         flush_messages();
334         _send_everything();
335         flush_messages();
336     }
337     else if (msgtype == "menu_scroll")
338     {
339         JsonWrapper first = json_find_member(obj.node, "first");
340         first.check(JSON_NUMBER);
341         // last visible item is sent too, but currently unused
342
343         if (!m_menu_stack.empty() && m_menu_stack.back().menu != NULL)
344             m_menu_stack.back().menu->webtiles_scroll((int) first->number_);
345     }
346     else if (msgtype == "*request_menu_range")
347     {
348         JsonWrapper start = json_find_member(obj.node, "start");
349         start.check(JSON_NUMBER);
350         JsonWrapper end = json_find_member(obj.node, "end");
351         end.check(JSON_NUMBER);
352
353         if (!m_menu_stack.empty() && m_menu_stack.back().menu != NULL)
354         {
355             m_menu_stack.back().menu->webtiles_handle_item_request((int) start->number_,
356                                                                    (int) end->number_);
357         }
358     }
359     else if (msgtype == "note")
360     {
361         JsonWrapper content = json_find_member(obj.node, "content");
362         content.check(JSON_STRING);
363
364         if (Options.note_chat_messages)
365             take_note(Note(NOTE_MESSAGE, MSGCH_PLAIN, 0, content->string_));
366     }
367
368     return c;
369 }
370
371 bool TilesFramework::await_input(wint_t& c, bool block)
372 {
373     int result;
374     fd_set fds;
375     int maxfd = m_sock;
376
377     while (true)
378     {
379         do
380         {
381             FD_ZERO(&fds);
382             FD_SET(STDIN_FILENO, &fds);
383             FD_SET(m_sock, &fds);
384
385             if (block)
386             {
387                 tiles.flush_messages();
388                 result = select(maxfd + 1, &fds, NULL, NULL, NULL);
389             }
390             else
391             {
392                 timeval timeout;
393                 timeout.tv_sec = 0;
394                 timeout.tv_usec = 0;
395
396                 result = select(maxfd + 1, &fds, NULL, NULL, &timeout);
397             }
398         }
399         while (result == -1 && errno == EINTR);
400
401         if (result == 0)
402             return false;
403         else if (result > 0)
404         {
405             if (FD_ISSET(m_sock, &fds))
406             {
407                 c = _receive_control_message();
408
409                 if (c != 0)
410                     return true;
411             }
412
413             if (FD_ISSET(STDIN_FILENO, &fds))
414             {
415                 c = 0;
416                 return true;
417             }
418         }
419         else if (errno == EBADF)
420         {
421             // This probably means that stdin got closed because of a
422             // SIGHUP. We'll just return.
423             c = 0;
424             return false;
425         }
426         else
427             die("select error: %s", strerror(errno));
428     }
429 }
430
431 void TilesFramework::dump()
432 {
433     fprintf(stderr, "Webtiles message buffer: %s\n", m_msg_buf.c_str());
434     fprintf(stderr, "Webtiles JSON stack:\n");
435     for (unsigned int i = 0; i < m_json_stack.size(); ++i)
436     {
437         fprintf(stderr, "start: %d end: %d type: %c\n",
438                 m_json_stack[i].start, m_json_stack[i].prefix_end,
439                 m_json_stack[i].type);
440     }
441 }
442
443 void TilesFramework::send_exit_reason(const string& type, const string& message)
444 {
445     write_message("*");
446     write_message("{\"msg\":\"exit_reason\",\"type\":\"");
447     write_message_escaped(type);
448     if (!message.empty())
449     {
450         write_message("\",\"message\":\"");
451         write_message_escaped(message);
452     }
453     write_message("\"}");
454     finish_message();
455 }
456
457 void TilesFramework::send_dump_info(const string& type, const string& filename)
458 {
459     write_message("*");
460     write_message("{\"msg\":\"dump\",\"type\":\"");
461     write_message_escaped(type);
462     write_message("\",\"filename\":\"");
463     write_message_escaped(strip_filename_unsafe_chars(filename));
464     write_message("\"}");
465     finish_message();
466 }
467
468 void TilesFramework::_send_version()
469 {
470 #ifdef WEB_DIR_PATH
471     // The star signals a message to the server
472     send_message("*{\"msg\":\"client_path\",\"path\":\"%s\",\"version\":\"%s\"}", WEB_DIR_PATH, Version::Long);
473 #endif
474
475     string title = CRAWL " " + string(Version::Long);
476     send_message("{\"msg\":\"version\",\"text\":\"%s\"}", title.c_str());
477 }
478
479 void TilesFramework::_send_options()
480 {
481     json_open_object();
482     json_write_string("msg", "options");
483     Options.write_webtiles_options("options");
484     json_close_object();
485     finish_message();
486 }
487
488 void TilesFramework::push_menu(Menu* m)
489 {
490     MenuInfo mi;
491     mi.menu = m;
492     m_menu_stack.push_back(mi);
493     m->webtiles_write_menu();
494     tiles.finish_message();
495 }
496
497 void TilesFramework::push_crt_menu(string tag)
498 {
499     MenuInfo mi;
500     mi.menu = NULL;
501     mi.tag = tag;
502     m_menu_stack.push_back(mi);
503
504     json_open_object();
505     json_write_string("msg", "menu");
506     json_write_string("type", "crt");
507     json_write_string("tag", tag);
508     json_close_object();
509     finish_message();
510 }
511
512 bool TilesFramework::is_in_crt_menu()
513 {
514     return is_in_menu(NULL);
515 }
516
517 bool TilesFramework::is_in_menu(Menu* m)
518 {
519     return !m_menu_stack.empty() && m_menu_stack.back().menu == m;
520 }
521
522 void TilesFramework::pop_menu()
523 {
524     if (m_menu_stack.empty()) return;
525     MenuInfo mi = m_menu_stack.back();
526     m_menu_stack.pop_back();
527     send_message("{\"msg\":\"close_menu\"}");
528 }
529
530 void TilesFramework::close_all_menus()
531 {
532     while (m_menu_stack.size())
533         pop_menu();
534 }
535
536 static void _send_ui_state(WebtilesUIState state)
537 {
538     tiles.json_open_object();
539     tiles.json_write_string("msg", "ui_state");
540     tiles.json_write_int("state", state);
541     tiles.json_close_object();
542     tiles.finish_message();
543 }
544
545 void TilesFramework::set_ui_state(WebtilesUIState state)
546 {
547     if (m_ui_state == state) return;
548
549     m_ui_state = state;
550 }
551
552 void TilesFramework::update_input_mode(mouse_mode mode)
553 {
554     redraw();
555
556     json_open_object();
557     json_write_string("msg", "input_mode");
558     json_write_int("mode", mode);
559     json_close_object();
560     finish_message();
561 }
562
563 static bool _update_string(bool force, string& current,
564                            const string& next,
565                            const string& name,
566                            bool update = true)
567 {
568     if (force || current != next)
569     {
570         tiles.json_write_string(name, next);
571         if (update)
572             current = next;
573         return true;
574     }
575     else
576         return false;
577 }
578
579 template<class T> static bool _update_int(bool force, T& current, T next,
580                                           const string& name,
581                                           bool update = true)
582 {
583     if (force || current != next)
584     {
585         tiles.json_write_int(name, next);
586         if (update)
587             current = next;
588         return true;
589     }
590     else
591         return false;
592 }
593
594 static bool _update_statuses(player_info& c)
595 {
596     bool changed = false;
597     unsigned int counter = 0;
598     status_info inf;
599     for (unsigned int status = 0; status <= STATUS_LAST_STATUS; ++status)
600     {
601         if (status == DUR_CONDENSATION_SHIELD || status == DUR_DIVINE_SHIELD)
602         {
603             if (!you.duration[status])
604                 continue;
605             inf.short_text = "shielded";
606         }
607         else if (status == DUR_ICEMAIL_DEPLETED)
608         {
609             if (you.duration[status] <= ICEMAIL_TIME / ICEMAIL_MAX)
610                 continue;
611             inf.short_text = "icemail depleted";
612         }
613         else if (!fill_status_info(status, &inf))
614             continue;
615
616         if (!inf.light_text.empty() || !inf.short_text.empty())
617         {
618             if (!changed)
619             {
620                 if (counter >= c.status.size()
621                     || inf.light_text != c.status[counter].light_text
622                     || inf.light_colour != c.status[counter].light_colour
623                     || inf.short_text != c.status[counter].short_text)
624                 {
625                     changed = true;
626                 }
627             }
628
629             if (changed)
630             {
631                 c.status.resize(counter + 1);
632                 c.status[counter] = inf;
633             }
634
635             counter++;
636         }
637     }
638     if (c.status.size() != counter)
639     {
640         ASSERT(!changed);
641         changed = true;
642         c.status.resize(counter);
643     }
644
645     return changed;
646 }
647
648 player_info::player_info()
649 {
650     for (unsigned int i = 0; i < NUM_EQUIP; ++i)
651         equip[i] = -1;
652     position = coord_def(-1, -1);
653 }
654
655 /**
656  * Send the player properties to the webserver.  Any player properties that
657  * must be available to the WebTiles client must be sent here through an
658  * _update_* function call of the correct data type.
659  * @param force_full  If true, all properties will be updated in the json
660  *                    regardless whether their values are the same as the
661  *                    current info in m_current_player_info.
662  */
663 void TilesFramework::_send_player(bool force_full)
664 {
665     player_info& c = m_current_player_info;
666
667     json_open_object();
668     json_write_string("msg", "player");
669     json_treat_as_empty();
670
671     _update_string(force_full, c.name, you.your_name, "name");
672     _update_string(force_full, c.job_title, filtered_lang(player_title()),
673                    "title");
674     _update_int(force_full, c.wizard, you.wizard, "wizard");
675     _update_string(force_full, c.species, species_name(you.species),
676                    "species");
677     string god = "";
678     if (you_worship(GOD_JIYVA))
679         god = god_name_jiyva(true);
680     else if (!you_worship(GOD_NO_GOD))
681         god = god_name(you.religion);
682     _update_string(force_full, c.god, god, "god");
683     _update_int(force_full, c.under_penance, (bool) player_under_penance(), "penance");
684     uint8_t prank = 0;
685     if (you_worship(GOD_XOM))
686     {
687         if (!you.gift_timeout)
688             prank = 2;
689         else if (you.gift_timeout == 1)
690             prank = 1;
691     }
692     else if (!you_worship(GOD_NO_GOD))
693         prank = max(0, piety_rank() - 1);
694     else if (you.char_class == JOB_MONK && you.species != SP_DEMIGOD
695              && !had_gods())
696     {
697         prank = 2;
698     }
699     _update_int(force_full, c.piety_rank, prank, "piety_rank");
700
701     _update_int(force_full, c.form, (uint8_t) you.form, "form");
702
703     _update_int(force_full, c.hp, you.hp, "hp");
704     _update_int(force_full, c.hp_max, you.hp_max, "hp_max");
705     int max_max_hp = get_real_hp(true, true);
706 #if TAG_MAJOR_VERSION == 34
707     if (you.species == SP_DJINNI)
708         max_max_hp += get_real_mp(true); // compare _print_stats_hp
709
710     _update_int(force_full, c.real_hp_max, max_max_hp, "real_hp_max");
711
712     if (you.species != SP_DJINNI)
713     {
714         _update_int(force_full, c.mp, you.magic_points, "mp");
715         _update_int(force_full, c.mp_max, you.max_magic_points, "mp_max");
716     }
717
718     if (you.species == SP_DJINNI)
719     {
720         // Don't send more information than can be seen from the console HUD.
721         // Compare _print_stats_contam and get_contamination_level
722         int contam = you.magic_contamination;
723         if (contam >= 26000)
724             contam = 26000;
725         else if (contam >= 16000)
726             contam = 16000;
727         _update_int(force_full, c.contam, contam, "contam");
728     }
729 #else
730     _update_int(force_full, c.real_hp_max, max_max_hp, "real_hp_max");
731     _update_int(force_full, c.mp, you.magic_points, "mp");
732     _update_int(force_full, c.mp_max, you.max_magic_points, "mp_max");
733 #endif
734     _update_int(force_full, c.poison_survival, max(0, poison_survival()),
735                 "poison_survival");
736
737 #if TAG_MAJOR_VERSION == 34
738     if (you.species == SP_LAVA_ORC)
739         _update_int(force_full, c.heat, temperature(), "heat");
740 #endif
741
742     _update_int(force_full, c.armour_class, you.armour_class(), "ac");
743     _update_int(force_full, c.evasion, player_evasion(), "ev");
744     _update_int(force_full, c.shield_class, player_displayed_shield_class(),
745                 "sh");
746
747     _update_int(force_full, c.strength, (int8_t) you.strength(false), "str");
748     _update_int(force_full, c.strength_max, (int8_t) you.max_strength(), "str_max");
749     _update_int(force_full, c.intel, (int8_t) you.intel(false), "int");
750     _update_int(force_full, c.intel_max, (int8_t) you.max_intel(), "int_max");
751     _update_int(force_full, c.dex, (int8_t) you.dex(false), "dex");
752     _update_int(force_full, c.dex_max, (int8_t) you.max_dex(), "dex_max");
753
754     if (you.species == SP_FELID)
755     {
756         _update_int(force_full, c.lives, you.lives, "lives");
757         _update_int(force_full, c.deaths, you.deaths, "deaths");
758     }
759
760     _update_int(force_full, c.experience_level, you.experience_level, "xl");
761     _update_int(force_full, c.exp_progress, (int8_t) get_exp_progress(), "progress");
762     _update_int(force_full, c.gold, you.gold, "gold");
763
764     if (crawl_state.game_is_zotdef())
765         _update_int(force_full, c.zot_points, you.zot_points, "zp");
766     if (you.running == 0) // Don't update during running/resting
767     {
768         _update_int(force_full, c.elapsed_time, you.elapsed_time, "time");
769         _update_int(force_full, c.num_turns, you.num_turns, "turn");
770     }
771
772     const PlaceInfo& place = you.get_place_info();
773     string short_name = branches[place.branch].shortname;
774
775     if (brdepth[place.branch] == 1)
776     {
777         // Definite articles
778         if (place.branch == BRANCH_ABYSS)
779             short_name.insert(0, "The ");
780         // Indefinite articles
781         else if (place.branch != BRANCH_PANDEMONIUM &&
782                  !is_connected_branch(place.branch))
783         {
784             short_name = article_a(short_name);
785         }
786     }
787     _update_string(force_full, c.place, short_name, "place");
788     _update_int(force_full, c.depth, brdepth[place.branch] > 1 ? you.depth : 0, "depth");
789
790     if (m_origin.equals(-1, -1))
791         m_origin = you.position;
792     coord_def pos = you.position - m_origin;
793     if (force_full || c.position != pos)
794     {
795         json_open_object("pos");
796         json_write_int("x", pos.x);
797         json_write_int("y", pos.y);
798         json_close_object();
799         c.position = pos;
800     }
801
802     if (force_full || _update_statuses(c))
803     {
804         json_open_array("status");
805         for (unsigned int i = 0; i < c.status.size(); ++i)
806         {
807             json_open_object();
808             if (!c.status[i].light_text.empty())
809                 json_write_string("light", c.status[i].light_text);
810             if (!c.status[i].short_text.empty())
811                 json_write_string("text", c.status[i].short_text);
812             if (c.status[i].light_colour)
813                 json_write_int("col", macro_colour(c.status[i].light_colour));
814             json_close_object(true);
815         }
816         json_close_array();
817     }
818
819     json_open_object("inv");
820     for (unsigned int i = 0; i < ENDOFPACK; ++i)
821     {
822         json_open_object(make_stringf("%d", i));
823         _send_item(c.inv[i], get_item_info(you.inv[i]), force_full);
824         json_close_object(true);
825     }
826     json_close_object(true);
827
828     json_open_object("equip");
829     for (unsigned int i = 0; i < NUM_EQUIP; ++i)
830     {
831         _update_int(force_full, c.equip[i], you.equip[i],
832                     make_stringf("%d", i));
833     }
834     json_close_object(true);
835
836     _update_int(force_full, c.quiver_item,
837                 (int8_t) you.m_quiver->get_fire_item(), "quiver_item");
838
839     _update_string(force_full, c.unarmed_attack,
840                    you.unarmed_attack_name(), "unarmed_attack");
841
842     json_close_object(true);
843
844     finish_message();
845 }
846
847 void TilesFramework::_send_item(item_info& current, const item_info& next,
848                                 bool force_full)
849 {
850     bool changed = false;
851     bool defined = next.defined();
852
853     if (force_full || current.base_type != next.base_type)
854     {
855         changed = true;
856         json_write_int("base_type", next.base_type);
857     }
858
859     changed |= _update_int(force_full, current.quantity, next.quantity,
860                            "quantity", false);
861
862     if (!defined)
863     {
864         current = next;
865         return; // For undefined items, only send base_type and quantity
866     }
867     else if (!current.defined())
868         force_full = true; // if the item was undefined before, send everything
869
870     changed |= _update_int(force_full, current.sub_type, next.sub_type,
871                            "sub_type", false);
872     changed |= _update_int(force_full, current.plus, next.plus,
873                            "plus", false);
874     changed |= _update_int(force_full, current.plus2, next.plus2,
875                            "plus2", false);
876     changed |= _update_int(force_full, current.flags, next.flags,
877                            "flags", false);
878     changed |= _update_string(force_full, current.inscription,
879                               next.inscription, "inscription", false);
880
881     // TODO: props?
882
883     changed |= (current.special != next.special);
884
885     // Derived stuff
886     if (changed && defined)
887     {
888         string name = next.name(DESC_A, true, false, true);
889         if (force_full || current.name(DESC_A, true, false, true) != name)
890             json_write_string("name", name);
891
892         const string prefix = item_prefix(next);
893         const int prefcol = menu_colour(next.name(DESC_INVENTORY), prefix);
894         if (force_full)
895             json_write_int("col", macro_colour(prefcol));
896         else
897         {
898             const string current_prefix = item_prefix(current);
899             const int current_prefcol = menu_colour(current.name(DESC_INVENTORY), current_prefix);
900
901             if (current_prefcol != prefcol)
902                 json_write_int("col", macro_colour(prefcol));
903         }
904
905         tileidx_t tile = tileidx_item(next);
906         if (force_full || tileidx_item(current) != tile)
907         {
908             json_open_array("tile");
909             tileidx_t base_tile = tileidx_known_base_item(tile);
910             if (base_tile)
911                 json_write_int(base_tile);
912             json_write_int(tile);
913             json_close_array();
914         }
915
916         current = next;
917     }
918 }
919
920 static void _send_doll(const dolls_data &doll, bool submerged, bool ghost)
921 {
922     // Ordered from back to front.
923     int p_order[TILEP_PART_MAX] =
924     {
925         // background
926         TILEP_PART_SHADOW,
927         TILEP_PART_HALO,
928         TILEP_PART_ENCH,
929         TILEP_PART_DRCWING,
930         TILEP_PART_CLOAK,
931         // player
932         TILEP_PART_BASE,
933         TILEP_PART_BOOTS,
934         TILEP_PART_LEG,
935         TILEP_PART_BODY,
936         TILEP_PART_ARM,
937         TILEP_PART_HAIR,
938         TILEP_PART_BEARD,
939         TILEP_PART_HELM,
940         TILEP_PART_HAND1,
941         TILEP_PART_HAND2,
942         TILEP_PART_DRCHEAD
943     };
944
945     int flags[TILEP_PART_MAX];
946     tilep_calc_flags(doll, flags);
947
948     // For skirts, boots go under the leg armour.  For pants, they go over.
949     if (doll.parts[TILEP_PART_LEG] < TILEP_LEG_SKIRT_OFS)
950     {
951         p_order[7] = TILEP_PART_BOOTS;
952         p_order[6] = TILEP_PART_LEG;
953     }
954
955     // Special case bardings from being cut off.
956     const bool is_naga = is_player_tile(doll.parts[TILEP_PART_BASE],
957                                         TILEP_BASE_NAGA);
958
959     if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_NAGA_BARDING
960         && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_NAGA_BARDING_RED)
961     {
962         flags[TILEP_PART_BOOTS] = is_naga ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
963     }
964
965     const bool is_cent = is_player_tile(doll.parts[TILEP_PART_BASE],
966                                         TILEP_BASE_CENTAUR);
967
968     if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_CENTAUR_BARDING
969         && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_CENTAUR_BARDING_RED)
970     {
971         flags[TILEP_PART_BOOTS] = is_cent ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
972     }
973
974     tiles.json_open_array("doll");
975
976     for (int i = 0; i < TILEP_PART_MAX; ++i)
977     {
978         int p = p_order[i];
979
980         if (!doll.parts[p] || flags[p] == TILEP_FLAG_HIDE)
981             continue;
982
983         if (p == TILEP_PART_SHADOW && (submerged || ghost))
984             continue;
985
986         int ymax = TILE_Y;
987
988         if (flags[p] == TILEP_FLAG_CUT_CENTAUR
989             || flags[p] == TILEP_FLAG_CUT_NAGA)
990         {
991             ymax = 18;
992         }
993
994         tiles.json_write_comma();
995         tiles.write_message("[%u,%d]", (unsigned int) doll.parts[p], ymax);
996     }
997     tiles.json_close_array();
998 }
999
1000 static void _send_mcache(mcache_entry *entry, bool submerged,
1001                          bool send_doll = true)
1002 {
1003     bool trans = entry->transparent();
1004     if (trans && send_doll)
1005         tiles.json_write_int("trans", 1);
1006
1007     const dolls_data *doll = entry->doll();
1008     if (send_doll)
1009     {
1010         if (doll)
1011             _send_doll(*doll, submerged, trans);
1012         else
1013         {
1014             tiles.json_write_comma();
1015             tiles.write_message("\"doll\":[]");
1016         }
1017     }
1018
1019     tiles.json_open_array("mcache");
1020
1021     tile_draw_info dinfo[mcache_entry::MAX_INFO_COUNT];
1022     int draw_info_count = entry->info(&dinfo[0]);
1023     for (int i = 0; i < draw_info_count; i++)
1024     {
1025         tiles.json_write_comma();
1026         tiles.write_message("[%u,%d,%d]", (unsigned int) dinfo[i].idx,
1027                             dinfo[i].ofs_x, dinfo[i].ofs_y);
1028     }
1029
1030     tiles.json_close_array();
1031 }
1032
1033 static bool _in_water(const packed_cell &cell)
1034 {
1035     return (cell.bg & TILE_FLAG_WATER) && !(cell.fg & TILE_FLAG_FLYING);
1036 }
1037
1038 static bool _needs_flavour(const packed_cell &cell)
1039 {
1040     tileidx_t bg_idx = cell.bg & TILE_FLAG_MASK;
1041     if (bg_idx >= TILE_DNGN_FIRST_TRANSPARENT)
1042         return true; // Needs flv.floor
1043     if (cell.is_liquefied || cell.is_bloody ||
1044         cell.is_moldy || cell.glowing_mold)
1045     {
1046         return true; // Needs flv.special
1047     }
1048     return false;
1049 }
1050
1051 static inline unsigned _get_brand(int col)
1052 {
1053     return (col & COLFLAG_FRIENDLY_MONSTER) ? Options.friend_brand :
1054            (col & COLFLAG_NEUTRAL_MONSTER)  ? Options.neutral_brand :
1055            (col & COLFLAG_ITEM_HEAP)        ? Options.heap_brand :
1056            (col & COLFLAG_WILLSTAB)         ? Options.stab_brand :
1057            (col & COLFLAG_MAYSTAB)          ? Options.may_stab_brand :
1058            (col & COLFLAG_FEATURE_ITEM)     ? Options.feature_item_brand :
1059            (col & COLFLAG_TRAP_ITEM)        ? Options.trap_item_brand :
1060            (col & COLFLAG_REVERSE)          ? CHATTR_REVERSE
1061                                             : CHATTR_NORMAL;
1062 }
1063
1064 static inline void _write_tileidx(tileidx_t t)
1065 {
1066     // JS can only handle signed ints
1067     const int lo = t & 0xFFFFFFFF;
1068     const int hi = t >> 32;
1069     if (hi == 0)
1070         tiles.write_message("%d", lo);
1071     else
1072         tiles.write_message("[%d,%d]", lo, hi);
1073 }
1074
1075 void TilesFramework::_send_cell(const coord_def &gc,
1076                                 const screen_cell_t &current_sc, const screen_cell_t &next_sc,
1077                                 const map_cell &current_mc, const map_cell &next_mc,
1078                                 map<uint32_t, coord_def>& new_monster_locs,
1079                                 bool force_full)
1080 {
1081     if (current_mc.feat() != next_mc.feat())
1082         json_write_int("f", next_mc.feat());
1083
1084     if (next_mc.monsterinfo())
1085         _send_monster(gc, next_mc.monsterinfo(), new_monster_locs, force_full);
1086     else if (current_mc.monsterinfo())
1087         json_write_null("mon");
1088
1089     map_feature mf = get_cell_map_feature(next_mc);
1090     if (get_cell_map_feature(current_mc) != mf)
1091         json_write_int("mf", mf);
1092
1093     // Glyph and colour
1094     ucs_t glyph = next_sc.glyph;
1095     if (current_sc.glyph != glyph)
1096     {
1097         char buf[5];
1098         buf[wctoutf8(buf, glyph)] = 0;
1099         json_write_string("g", buf);
1100     }
1101     if ((current_sc.colour != next_sc.colour
1102          || current_sc.glyph == ' ') && glyph != ' ')
1103     {
1104         int col = next_sc.colour;
1105         col = (_get_brand(col) << 4) | macro_colour(col & 0xF);
1106         json_write_int("col", col);
1107     }
1108
1109     json_open_object("t");
1110     {
1111         // Tile data
1112         const packed_cell &next_pc = next_sc.tile;
1113         const packed_cell &current_pc = current_sc.tile;
1114
1115         const tileidx_t fg_idx = next_pc.fg & TILE_FLAG_MASK;
1116
1117         const bool in_water = _in_water(next_pc);
1118         bool fg_changed = false;
1119
1120         if (next_pc.fg != current_pc.fg)
1121         {
1122             fg_changed = true;
1123
1124             json_write_name("fg");
1125             _write_tileidx(next_pc.fg);
1126             if (fg_idx && fg_idx <= TILE_MAIN_MAX)
1127                 json_write_int("base", (int) tileidx_known_base_item(fg_idx));
1128         }
1129
1130         if (next_pc.bg != current_pc.bg)
1131         {
1132             json_write_name("bg");
1133             _write_tileidx(next_pc.bg);
1134         }
1135
1136         if (next_pc.cloud != current_pc.cloud)
1137         {
1138             json_write_name("cloud");
1139             _write_tileidx(next_pc.cloud);
1140         }
1141
1142         if (next_pc.is_bloody != current_pc.is_bloody)
1143             json_write_bool("bloody", next_pc.is_bloody);
1144
1145         if (next_pc.old_blood != current_pc.old_blood)
1146             json_write_bool("old_blood", next_pc.old_blood);
1147
1148         if (next_pc.is_silenced != current_pc.is_silenced)
1149             json_write_bool("silenced", next_pc.is_silenced);
1150
1151         if (next_pc.halo != current_pc.halo)
1152             json_write_int("halo", next_pc.halo);
1153
1154         if (next_pc.is_moldy != current_pc.is_moldy)
1155             json_write_bool("moldy", next_pc.is_moldy);
1156
1157         if (next_pc.glowing_mold != current_pc.glowing_mold)
1158             json_write_bool("glowing_mold", next_pc.glowing_mold);
1159
1160         if (next_pc.is_sanctuary != current_pc.is_sanctuary)
1161             json_write_bool("sanctuary", next_pc.is_sanctuary);
1162
1163         if (next_pc.is_liquefied != current_pc.is_liquefied)
1164             json_write_bool("liquefied", next_pc.is_liquefied);
1165
1166         if (next_pc.orb_glow != current_pc.orb_glow)
1167             json_write_int("orb_glow", next_pc.orb_glow);
1168
1169         if (next_pc.quad_glow != current_pc.quad_glow)
1170             json_write_bool("quad_glow", next_pc.quad_glow);
1171
1172         if (next_pc.disjunct != current_pc.disjunct)
1173             json_write_bool("disjunct", next_pc.disjunct);
1174
1175         if (next_pc.mangrove_water != current_pc.mangrove_water)
1176             json_write_bool("mangrove_water", next_pc.mangrove_water);
1177
1178         if (next_pc.blood_rotation != current_pc.blood_rotation)
1179             json_write_int("blood_rotation", next_pc.blood_rotation);
1180
1181         if (next_pc.travel_trail != current_pc.travel_trail)
1182             json_write_int("travel_trail", next_pc.travel_trail);
1183
1184 #if TAG_MAJOR_VERSION == 34
1185         if (next_pc.heat_aura != current_pc.heat_aura)
1186             json_write_int("heat_aura", next_pc.heat_aura);
1187 #endif
1188
1189         if (next_pc.gold_aura != current_pc.gold_aura)
1190             json_write_int("gold_aura", next_pc.gold_aura);
1191
1192         if (_needs_flavour(next_pc) &&
1193             (next_pc.flv.floor != current_pc.flv.floor
1194              || next_pc.flv.special != current_pc.flv.special
1195              || !_needs_flavour(current_pc)
1196              || force_full))
1197         {
1198             json_open_object("flv");
1199             json_write_int("f", next_pc.flv.floor);
1200             if (next_pc.flv.special)
1201                 json_write_int("s", next_pc.flv.special);
1202             json_close_object();
1203         }
1204
1205         if (fg_idx >= TILEP_MCACHE_START)
1206         {
1207             if (fg_changed)
1208             {
1209                 mcache_entry *entry = mcache.get(fg_idx);
1210                 if (entry)
1211                     _send_mcache(entry, in_water);
1212                 else
1213                 {
1214                     json_write_comma();
1215                     write_message("\"doll\":[[%d,%d]]", TILEP_MONS_UNKNOWN, TILE_Y);
1216                 }
1217             }
1218         }
1219         else if (fg_idx == TILEP_PLAYER)
1220         {
1221             bool player_doll_changed = false;
1222             dolls_data result = player_doll;
1223             fill_doll_equipment(result);
1224             if (result != last_player_doll)
1225             {
1226                 player_doll_changed = true;
1227                 last_player_doll = result;
1228             }
1229             if (fg_changed || player_doll_changed)
1230             {
1231                 _send_doll(last_player_doll, in_water, false);
1232                 if (Options.tile_use_monster != MONS_0)
1233                 {
1234                     monster_info minfo(MONS_PLAYER, MONS_PLAYER);
1235                     minfo.props["monster_tile"] =
1236                         short(last_player_doll.parts[TILEP_PART_BASE]);
1237                     item_def *item;
1238                     if (you.slot_item(EQ_WEAPON))
1239                     {
1240                         item = new item_def(get_item_info(*you.slot_item(EQ_WEAPON)));
1241                         minfo.inv[MSLOT_WEAPON].reset(item);
1242                     }
1243                     if (you.slot_item(EQ_SHIELD))
1244                     {
1245                         item = new item_def(get_item_info(*you.slot_item(EQ_SHIELD)));
1246                         minfo.inv[MSLOT_SHIELD].reset(item);
1247                     }
1248                     tileidx_t mcache_idx = mcache.register_monster(minfo);
1249                     mcache_entry *entry = mcache.get(mcache_idx);
1250                     if (entry)
1251                         _send_mcache(entry, in_water, false);
1252                 }
1253             }
1254         }
1255         else if (fg_idx >= TILE_MAIN_MAX)
1256         {
1257             if (fg_changed)
1258             {
1259                 json_write_comma();
1260                 write_message("\"doll\":[[%u,%d]]", (unsigned int) fg_idx, TILE_Y);
1261             }
1262         }
1263
1264         bool overlays_changed = false;
1265
1266         if (next_pc.num_dngn_overlay != current_pc.num_dngn_overlay)
1267             overlays_changed = true;
1268         else
1269         {
1270             for (int i = 0; i < next_pc.num_dngn_overlay; i++)
1271             {
1272                 if (next_pc.dngn_overlay[i] != current_pc.dngn_overlay[i])
1273                 {
1274                     overlays_changed = true;
1275                     break;
1276                 }
1277             }
1278         }
1279
1280         if (overlays_changed)
1281         {
1282             json_open_array("ov");
1283             for (int i = 0; i < next_pc.num_dngn_overlay; ++i)
1284                 json_write_int(next_pc.dngn_overlay[i]);
1285             json_close_array();
1286         }
1287     }
1288     json_close_object(true);
1289 }
1290
1291 void TilesFramework::_send_cursor(cursor_type type)
1292 {
1293     if (m_cursor[type] == NO_CURSOR)
1294         send_message("{\"msg\":\"cursor\",\"id\":%d}", type);
1295     else
1296     {
1297         if (m_origin.equals(-1, -1))
1298             m_origin = m_cursor[type];
1299         send_message("{\"msg\":\"cursor\",\"id\":%d,\"loc\":{\"x\":%d,\"y\":%d}}",
1300                      type, m_cursor[type].x - m_origin.x,
1301                      m_cursor[type].y - m_origin.y);
1302     }
1303 }
1304
1305 void TilesFramework::_mcache_ref(bool inc)
1306 {
1307     for (int y = 0; y < GYM; y++)
1308         for (int x = 0; x < GXM; x++)
1309         {
1310             coord_def gc(x, y);
1311
1312             int fg_idx = m_current_view(gc).tile.fg & TILE_FLAG_MASK;
1313             if (fg_idx >= TILEP_MCACHE_START)
1314             {
1315                 mcache_entry *entry = mcache.get(fg_idx);
1316                 if (entry)
1317                 {
1318                     if (inc)
1319                         entry->inc_ref();
1320                     else
1321                         entry->dec_ref();
1322                 }
1323             }
1324         }
1325 }
1326
1327 void TilesFramework::_send_map(bool force_full)
1328 {
1329     map<uint32_t, coord_def> new_monster_locs;
1330
1331     force_full = force_full || m_need_full_map;
1332     m_need_full_map = false;
1333
1334     json_open_object();
1335     json_write_string("msg", "map");
1336     json_treat_as_empty();
1337
1338     if (force_full)
1339         json_write_bool("clear", true);
1340
1341     if (force_full || you.on_current_level != m_player_on_level)
1342     {
1343         json_write_bool("player_on_level", you.on_current_level);
1344         m_player_on_level = you.on_current_level;
1345     }
1346
1347     if (force_full || m_current_gc != m_next_gc)
1348     {
1349         if (m_origin.equals(-1, -1))
1350             m_origin = m_next_gc;
1351         json_open_object("vgrdc");
1352         json_write_int("x", m_next_gc.x - m_origin.x);
1353         json_write_int("y", m_next_gc.y - m_origin.y);
1354         json_close_object();
1355         m_current_gc = m_next_gc;
1356     }
1357
1358     screen_cell_t default_cell;
1359     default_cell.tile.bg = TILE_FLAG_UNSEEN;
1360     default_cell.glyph = ' ';
1361     default_cell.colour = 7;
1362     map_cell default_map_cell;
1363
1364     coord_def last_gc(0, 0);
1365     bool send_gc = true;
1366
1367     json_open_array("cells");
1368     for (int y = 0; y < GYM; y++)
1369         for (int x = 0; x < GXM; x++)
1370         {
1371             coord_def gc(x, y);
1372
1373             if (!is_dirty(gc) && !force_full)
1374                 continue;
1375
1376             if (cell_needs_redraw(gc))
1377             {
1378                 screen_cell_t *cell = &m_next_view(gc);
1379
1380                 draw_cell(cell, gc, false, m_current_flash_colour);
1381                 cell->tile.flv = env.tile_flv(gc);
1382                 pack_cell_overlays(gc, &(cell->tile));
1383             }
1384
1385             mark_clean(gc);
1386
1387             if (m_origin.equals(-1, -1))
1388                 m_origin = gc;
1389
1390             json_open_object();
1391             if (send_gc
1392                 || last_gc.x + 1 != gc.x
1393                 || last_gc.y != gc.y)
1394             {
1395                 json_write_int("x", x - m_origin.x);
1396                 json_write_int("y", y - m_origin.y);
1397                 json_treat_as_empty();
1398             }
1399
1400             const screen_cell_t& sc = force_full ? default_cell
1401                 : m_current_view(gc);
1402             const map_cell& mc = force_full ? default_map_cell
1403                 : m_current_map_knowledge(gc);
1404             _send_cell(gc,
1405                        sc,
1406                        m_next_view(gc),
1407                        mc, env.map_knowledge(gc),
1408                        new_monster_locs, force_full);
1409
1410             if (!json_is_empty())
1411             {
1412                 send_gc = false;
1413                 last_gc = gc;
1414             }
1415             json_close_object(true);
1416         }
1417     json_close_array(true);
1418
1419     json_close_object(true);
1420
1421     finish_message();
1422
1423     if (force_full)
1424         _send_cursor(CURSOR_MAP);
1425
1426     if (m_mcache_ref_done)
1427         _mcache_ref(false);
1428
1429     m_current_map_knowledge = env.map_knowledge;
1430     m_current_view = m_next_view;
1431
1432     _mcache_ref(true);
1433     m_mcache_ref_done = true;
1434
1435     m_monster_locs = new_monster_locs;
1436 }
1437
1438 void TilesFramework::_send_monster(const coord_def &gc, const monster_info* m,
1439                                    map<uint32_t, coord_def>& new_monster_locs,
1440                                    bool force_full)
1441 {
1442     json_open_object("mon");
1443     if (m->client_id)
1444     {
1445         json_write_int("id", m->client_id);
1446         json_treat_as_empty();
1447         new_monster_locs[m->client_id] = gc;
1448     }
1449
1450     const monster_info* last = NULL;
1451     map<uint32_t, coord_def>::const_iterator it =
1452         m_monster_locs.find(m->client_id);
1453     if (m->client_id == 0 || it == m_monster_locs.end())
1454     {
1455         last = m_current_map_knowledge(gc).monsterinfo();
1456
1457         if (last && last->client_id != m->client_id)
1458             json_treat_as_nonempty(); // Force sending at least the id
1459     }
1460     else
1461     {
1462         last = m_current_map_knowledge(it->second).monsterinfo();
1463
1464         if (it->second != gc)
1465             json_treat_as_nonempty(); // As above
1466     }
1467
1468     if (last == NULL)
1469         force_full = true;
1470
1471     if (force_full || (last->full_name() != m->full_name()))
1472         json_write_string("name", m->full_name());
1473
1474     if (force_full || (last->pluralised_name() != m->pluralised_name()))
1475         json_write_string("plural", m->pluralised_name());
1476
1477     if (force_full || last->type != m->type)
1478     {
1479         json_write_int("type", m->type);
1480
1481         // TODO: get this information to the client in another way
1482         json_open_object("typedata");
1483         json_write_int("avghp", mons_avg_hp(m->type));
1484         if (mons_class_flag(m->type, M_NO_EXP_GAIN))
1485             json_write_bool("no_exp", true);
1486         json_close_object();
1487     }
1488
1489     if (force_full || last->attitude != m->attitude)
1490         json_write_int("att", m->attitude);
1491
1492     if (force_full || last->base_type != m->base_type)
1493         json_write_int("btype", m->base_type);
1494
1495     if (force_full || last->threat != m->threat)
1496         json_write_int("threat", m->threat);
1497
1498     json_close_object(true);
1499 }
1500
1501 void TilesFramework::load_dungeon(const crawl_view_buffer &vbuf,
1502                                   const coord_def &gc)
1503 {
1504     if (vbuf.size().equals(0, 0))
1505         return;
1506
1507     m_view_loaded = true;
1508
1509     if (m_ui_state == UI_CRT)
1510         set_ui_state(UI_NORMAL);
1511
1512     m_next_flash_colour = you.flash_colour;
1513     if (m_next_flash_colour == BLACK)
1514         m_next_flash_colour = viewmap_flash_colour();
1515
1516     // First re-render the area that was covered by vbuf the last time
1517     for (int y = m_next_view_tl.y; y <= m_next_view_br.y; y++)
1518         for (int x = m_next_view_tl.x; x <= m_next_view_br.x; x++)
1519         {
1520             if (x < 0 || x >= GXM || y < 0 || y >= GYM)
1521                 continue;
1522
1523             if (!crawl_view.in_viewport_g(coord_def(x, y)))
1524                 mark_for_redraw(coord_def(x, y));
1525         }
1526
1527     m_next_view_tl = view2grid(coord_def(1, 1));
1528     m_next_view_br = view2grid(crawl_view.viewsz);
1529
1530     // Copy vbuf into m_next_view
1531     for (int y = 0; y < vbuf.size().y; y++)
1532         for (int x = 0; x < vbuf.size().x; x++)
1533         {
1534             coord_def pos(x+1, y+1);
1535             coord_def grid = view2grid(pos);
1536
1537             if (grid.x < 0 || grid.x >= GXM || grid.y < 0 || grid.y >= GYM)
1538                 continue;
1539
1540             screen_cell_t *cell = &m_next_view(grid);
1541
1542             *cell = ((const screen_cell_t *) vbuf)[x + vbuf.size().x * y];
1543             cell->tile.flv = env.tile_flv(grid);
1544             pack_cell_overlays(grid, &(cell->tile));
1545
1546             mark_clean(grid); // Remove redraw flag
1547             mark_dirty(grid);
1548         }
1549
1550     m_next_gc = gc;
1551 }
1552
1553 void TilesFramework::load_dungeon(const coord_def &cen)
1554 {
1555     unwind_var<coord_def> viewp(crawl_view.viewp, cen - crawl_view.viewhalfsz);
1556     unwind_var<coord_def> vgrdc(crawl_view.vgrdc, cen);
1557     unwind_var<coord_def> vlos1(crawl_view.vlos1);
1558     unwind_var<coord_def> vlos2(crawl_view.vlos2);
1559
1560     m_next_gc = cen;
1561
1562     crawl_view.calc_vlos();
1563     viewwindow(false, true);
1564     place_cursor(CURSOR_MAP, cen);
1565 }
1566
1567 void TilesFramework::resize()
1568 {
1569     m_text_crt.resize(crawl_view.termsz.x, crawl_view.termsz.y);
1570     m_text_menu.resize(crawl_view.termsz.x, crawl_view.termsz.y);
1571 }
1572
1573 /*
1574   Send everything a newly joined spectator needs
1575  */
1576 void TilesFramework::_send_everything()
1577 {
1578     _send_version();
1579     _send_options();
1580
1581     // UI State
1582     _send_ui_state(m_ui_state);
1583     m_last_ui_state = m_ui_state;
1584
1585     send_message("{\"msg\":\"flash\",\"col\":%d}", m_current_flash_colour);
1586
1587     _send_map(true);
1588
1589     _send_cursor(CURSOR_MOUSE);
1590     _send_cursor(CURSOR_TUTORIAL);
1591
1592      // Player
1593     _send_player(true);
1594
1595     // Menus
1596     json_open_object();
1597     json_write_string("msg", "init_menus");
1598     json_open_array("menus");
1599     for (unsigned int i = 0; i < m_menu_stack.size(); ++i)
1600     {
1601         if (m_menu_stack[i].menu)
1602             m_menu_stack[i].menu->webtiles_write_menu();
1603         else
1604         {
1605             json_open_object();
1606             json_write_string("msg", "menu");
1607             json_write_string("type", "crt");
1608             json_write_string("tag", m_menu_stack[i].tag);
1609             json_close_object();
1610         }
1611     }
1612     json_close_array();
1613     json_close_object();
1614     finish_message();
1615
1616     webtiles_send_last_messages();
1617
1618     update_input_mode(mouse_control::current_mode());
1619
1620     m_text_crt.send(true);
1621     m_text_menu.send(true);
1622 }
1623
1624 void TilesFramework::clrscr()
1625 {
1626     m_text_crt.clear();
1627     m_text_menu.clear();
1628
1629     cgotoxy(1, 1);
1630
1631     set_need_redraw();
1632 }
1633
1634 void TilesFramework::cgotoxy(int x, int y, GotoRegion region)
1635 {
1636     m_print_x = x - 1;
1637     m_print_y = y - 1;
1638     switch (region)
1639     {
1640     case GOTO_CRT:
1641         switch (m_crt_mode)
1642         {
1643         case CRT_DISABLED:
1644             m_print_area = NULL;
1645             break;
1646         case CRT_NORMAL:
1647             set_ui_state(UI_CRT);
1648             m_print_area = &m_text_crt;
1649             break;
1650         case CRT_MENU:
1651             m_print_area = &m_text_menu;
1652             break;
1653         }
1654         break;
1655     case GOTO_STAT:
1656     case GOTO_MSG:
1657         set_ui_state(UI_NORMAL);
1658         m_print_area = NULL;
1659         break;
1660     default:
1661         m_print_area = NULL;
1662         break;
1663     }
1664     m_cursor_region = region;
1665 }
1666
1667 void TilesFramework::redraw()
1668 {
1669     if (!has_receivers())
1670     {
1671         if (m_mcache_ref_done)
1672         {
1673             _mcache_ref(false);
1674             m_mcache_ref_done = false;
1675         }
1676         return;
1677     }
1678
1679     if (m_last_ui_state != m_ui_state)
1680     {
1681         _send_ui_state(m_ui_state);
1682         m_last_ui_state = m_ui_state;
1683     }
1684
1685     m_text_crt.send();
1686     m_text_menu.send();
1687
1688     _send_player();
1689     webtiles_send_messages();
1690
1691     if (m_need_redraw && m_view_loaded)
1692     {
1693         if (m_current_flash_colour != m_next_flash_colour)
1694         {
1695             send_message("{\"msg\":\"flash\",\"col\":%d}",
1696                          m_next_flash_colour);
1697             m_current_flash_colour = m_next_flash_colour;
1698         }
1699         _send_map(false);
1700     }
1701
1702     m_need_redraw = false;
1703     m_last_tick_redraw = get_milliseconds();
1704 }
1705
1706 void TilesFramework::update_minimap(const coord_def& gc)
1707 {
1708     if (gc.x < 0 || gc.x >= GXM || gc.y < 0 || gc.y >= GYM)
1709         return;
1710
1711     mark_for_redraw(gc);
1712 }
1713
1714 void TilesFramework::clear_minimap()
1715 {
1716     m_origin = coord_def(-1, -1);
1717     // Changing the origin invalidates coordinates on the client side
1718     m_current_gc = coord_def(-1, -1);
1719     m_need_full_map = true;
1720 }
1721
1722 void TilesFramework::update_minimap_bounds()
1723 {
1724 }
1725
1726 void TilesFramework::update_tabs()
1727 {
1728 }
1729
1730 void TilesFramework::place_cursor(cursor_type type, const coord_def &gc)
1731 {
1732     // This is mainly copied from DungeonRegion::place_cursor.
1733     coord_def result = gc;
1734
1735     // If we're only looking for a direction, put the mouse
1736     // cursor next to the player to let them know that their
1737     // spell/wand will only go one square.
1738     if (mouse_control::current_mode() == MOUSE_MODE_TARGET_DIR
1739         && type == CURSOR_MOUSE && gc != INVALID_COORD)
1740     {
1741         coord_def delta = gc - you.pos();
1742
1743         int ax = abs(delta.x);
1744         int ay = abs(delta.y);
1745
1746         result = you.pos();
1747         if (1000 * ay < 414 * ax)
1748             result += (delta.x > 0) ? coord_def(1, 0) : coord_def(-1, 0);
1749         else if (1000 * ax < 414 * ay)
1750             result += (delta.y > 0) ? coord_def(0, 1) : coord_def(0, -1);
1751         else if (delta.x > 0)
1752             result += (delta.y > 0) ? coord_def(1, 1) : coord_def(1, -1);
1753         else if (delta.x < 0)
1754             result += (delta.y > 0) ? coord_def(-1, 1) : coord_def(-1, -1);
1755     }
1756
1757     if (m_cursor[type] != result)
1758     {
1759         m_cursor[type] = result;
1760         if (type == CURSOR_MOUSE)
1761             m_last_clicked_grid = coord_def();
1762
1763         // if map is going to be updated, send the cursor after that
1764         if (type == CURSOR_MAP && m_need_full_map)
1765             return;
1766
1767         _send_cursor(type);
1768     }
1769 }
1770
1771 void TilesFramework::clear_text_tags(text_tag_type type)
1772 {
1773 }
1774
1775 void TilesFramework::add_text_tag(text_tag_type type, const string &tag,
1776                                   const coord_def &gc)
1777 {
1778 }
1779
1780 void TilesFramework::add_text_tag(text_tag_type type, const monster_info& mon)
1781 {
1782 }
1783
1784 const coord_def &TilesFramework::get_cursor() const
1785 {
1786     return m_cursor[CURSOR_MOUSE];
1787 }
1788
1789 void TilesFramework::add_overlay(const coord_def &gc, tileidx_t idx)
1790 {
1791     if (idx >= TILE_MAIN_MAX)
1792         return;
1793
1794     m_has_overlays = true;
1795
1796     send_message("{\"msg\":\"overlay\",\"idx\":%u,\"x\":%d,\"y\":%d}",
1797                  (unsigned int) idx, gc.x - m_origin.x, gc.y - m_origin.y);
1798 }
1799
1800 void TilesFramework::clear_overlays()
1801 {
1802     if (m_has_overlays)
1803         send_message("{\"msg\":\"clear_overlays\"}");
1804
1805     m_has_overlays = false;
1806 }
1807
1808 void TilesFramework::set_need_redraw(unsigned int min_tick_delay)
1809 {
1810     unsigned int ticks = (get_milliseconds() - m_last_tick_redraw);
1811     if (min_tick_delay && ticks <= min_tick_delay)
1812         return;
1813
1814     m_need_redraw = true;
1815 }
1816
1817 bool TilesFramework::need_redraw() const
1818 {
1819     return m_need_redraw;
1820 }
1821
1822 void TilesFramework::textcolour(int col)
1823 {
1824     m_print_fg = col & 0xF;
1825     m_print_bg = (col >> 4) & 0xF;
1826 }
1827
1828 void TilesFramework::textbackground(int col)
1829 {
1830     m_print_bg = col;
1831 }
1832
1833 void TilesFramework::put_ucs_string(ucs_t *str)
1834 {
1835     if (m_print_area == NULL)
1836         return;
1837
1838     while (*str)
1839     {
1840         if (*str == '\r')
1841             continue;
1842
1843         if (*str == '\n')
1844         {
1845             m_print_x = 0;
1846             m_print_y++;
1847             // TODO: Clear end of line?
1848         }
1849         else
1850         {
1851             if (m_print_x >= m_print_area->mx)
1852             {
1853                 m_print_x = 0;
1854                 m_print_y++;
1855             }
1856
1857             if (m_print_y < m_print_area->my)
1858             {
1859                 m_print_area->put_character(*str, m_print_fg, m_print_bg,
1860                                             m_print_x, m_print_y);
1861             }
1862
1863             m_print_x++;
1864         }
1865
1866         str++;
1867     }
1868 }
1869
1870 void TilesFramework::clear_to_end_of_line()
1871 {
1872     if (m_print_area == NULL || m_print_y >= m_print_area->my)
1873         return;
1874
1875     for (int x = m_print_x; x < m_print_area->mx; ++x)
1876         m_print_area->put_character(' ', m_print_fg, m_print_bg, x, m_print_y);
1877 }
1878
1879 void TilesFramework::mark_for_redraw(const coord_def& gc)
1880 {
1881     mark_dirty(gc);
1882     m_cells_needing_redraw[gc.y * GXM + gc.x] = true;
1883 }
1884
1885 void TilesFramework::mark_dirty(const coord_def& gc)
1886 {
1887     m_dirty_cells[gc.y * GXM + gc.x] = true;
1888 }
1889
1890 void TilesFramework::mark_clean(const coord_def& gc)
1891 {
1892     m_cells_needing_redraw[gc.y * GXM + gc.x] = false;
1893     m_dirty_cells[gc.y * GXM + gc.x] = false;
1894 }
1895
1896 bool TilesFramework::is_dirty(const coord_def& gc)
1897 {
1898     return m_dirty_cells[gc.y * GXM + gc.x];
1899 }
1900
1901 bool TilesFramework::cell_needs_redraw(const coord_def& gc)
1902 {
1903     return m_cells_needing_redraw[gc.y * GXM + gc.x];
1904 }
1905
1906 void TilesFramework::write_message_escaped(const string& s)
1907 {
1908     m_msg_buf.reserve(m_msg_buf.size() + s.size());
1909
1910     for (size_t i = 0; i < s.size(); ++i)
1911     {
1912         unsigned char c = s[i];
1913         if (c == '"')
1914             m_msg_buf.append("\\\"");
1915         else if (c == '\\')
1916             m_msg_buf.append("\\\\");
1917         else if (c < 0x20)
1918         {
1919             char buf[7];
1920             snprintf(buf, sizeof(buf), "\\u%04x", c);
1921             m_msg_buf.append(buf);
1922         }
1923         else
1924             m_msg_buf.append(1, c);
1925     }
1926 }
1927
1928 void TilesFramework::json_open(const string& name, char opener, char type)
1929 {
1930     m_json_stack.resize(m_json_stack.size() + 1);
1931     JsonFrame& fr = m_json_stack.back();
1932     fr.start = m_msg_buf.size();
1933
1934     json_write_comma();
1935     if (!name.empty())
1936         json_write_name(name);
1937
1938     m_msg_buf.append(1, opener);
1939
1940     fr.prefix_end = m_msg_buf.size();
1941     fr.type = type;
1942 }
1943
1944 void TilesFramework::json_treat_as_empty()
1945 {
1946     if (m_json_stack.empty())
1947         die("json error: empty stack");
1948     m_json_stack.back().prefix_end = m_msg_buf.size();
1949 }
1950
1951 void TilesFramework::json_treat_as_nonempty()
1952 {
1953     if (m_json_stack.empty())
1954         die("json error: empty stack");
1955     m_json_stack.back().prefix_end = -1;
1956 }
1957
1958 bool TilesFramework::json_is_empty()
1959 {
1960     if (m_json_stack.empty())
1961         die("json error: empty stack");
1962     return m_json_stack.back().prefix_end == (int) m_msg_buf.size();
1963 }
1964
1965 void TilesFramework::json_close(bool erase_if_empty, char type)
1966 {
1967     if (m_json_stack.empty())
1968         die("json error: attempting to close object/array on empty stack");
1969     if (m_json_stack.back().type != type)
1970         die("json error: attempting to close wrong type");
1971
1972     if (erase_if_empty && json_is_empty())
1973         m_msg_buf.resize(m_json_stack.back().start);
1974     else
1975         m_msg_buf.append(1, type);
1976
1977     m_json_stack.pop_back();
1978 }
1979
1980 void TilesFramework::json_open_object(const string& name)
1981 {
1982     json_open(name, '{', '}');
1983 }
1984
1985 void TilesFramework::json_close_object(bool erase_if_empty)
1986 {
1987     json_close(erase_if_empty, '}');
1988 }
1989
1990 void TilesFramework::json_open_array(const string& name)
1991 {
1992     json_open(name, '[', ']');
1993 }
1994
1995 void TilesFramework::json_close_array(bool erase_if_empty)
1996 {
1997     json_close(erase_if_empty, ']');
1998 }
1999
2000 void TilesFramework::json_write_comma()
2001 {
2002     if (m_msg_buf.empty()) return;
2003     char last = m_msg_buf[m_msg_buf.size() - 1];
2004     if (last == '{' || last == '[' || last == ',' || last == ':') return;
2005     write_message(",");
2006 }
2007
2008 void TilesFramework::json_write_name(const string& name)
2009 {
2010     json_write_comma();
2011
2012     write_message("\"");
2013     write_message_escaped(name);
2014     write_message("\":");
2015 }
2016
2017 void TilesFramework::json_write_int(int value)
2018 {
2019     json_write_comma();
2020
2021     write_message("%d", value);
2022 }
2023
2024 void TilesFramework::json_write_int(const string& name, int value)
2025 {
2026     if (!name.empty())
2027         json_write_name(name);
2028
2029     json_write_int(value);
2030 }
2031
2032 void TilesFramework::json_write_bool(bool value)
2033 {
2034     json_write_comma();
2035
2036     if (value)
2037         write_message("true");
2038     else
2039         write_message("false");
2040 }
2041
2042 void TilesFramework::json_write_bool(const string& name, bool value)
2043 {
2044     if (!name.empty())
2045         json_write_name(name);
2046
2047     json_write_bool(value);
2048 }
2049
2050 void TilesFramework::json_write_null()
2051 {
2052     json_write_comma();
2053
2054     write_message("null");
2055 }
2056
2057 void TilesFramework::json_write_null(const string& name)
2058 {
2059     if (!name.empty())
2060         json_write_name(name);
2061
2062     json_write_null();
2063 }
2064
2065 void TilesFramework::json_write_string(const string& value)
2066 {
2067     json_write_comma();
2068
2069     write_message("\"");
2070     write_message_escaped(value);
2071     write_message("\"");
2072 }
2073
2074 void TilesFramework::json_write_string(const string& name, const string& value)
2075 {
2076     if (!name.empty())
2077         json_write_name(name);
2078
2079     json_write_string(value);
2080 }
2081
2082 bool is_tiles()
2083 {
2084     return tiles.is_controlled_from_web();
2085 }
2086 #endif