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