9 #include <sys/socket.h>
11 #include <sys/types.h>
24 #include "lang-fake.h"
26 #include "map_knowledge.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"
44 #include "tilepick-p.h"
53 static unsigned int get_milliseconds()
55 // This is Unix-only, but so is Webtiles at the moment.
57 gettimeofday(&tv, NULL);
59 return ((unsigned int) tv.tv_sec) * 1000 + tv.tv_usec / 1000;
65 JsonWrapper(JsonNode* n) : node(n)
74 JsonNode* operator->()
79 void check(JsonTag tag)
81 if (!node || node->tag != tag)
87 static class MalformedException { } malformed;
92 TilesFramework::TilesFramework()
93 : m_crt_mode(CRT_NORMAL),
94 m_controlled_from_web(false),
95 m_last_ui_state(UI_INIT),
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),
103 m_text_menu("menu_txt"),
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);
112 TilesFramework::~TilesFramework()
116 void TilesFramework::shutdown()
119 remove(m_sock_name.c_str());
122 void TilesFramework::draw_doll_edit()
126 bool TilesFramework::initialise()
129 m_sock = socket(PF_UNIX, SOCK_DGRAM, 0);
131 die("Can't open the webtiles socket!");
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!");
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;
147 if (setsockopt(m_sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
148 die("Can't set send timeout!");
150 if (m_await_connection)
154 send_exit_reason("unknown");
157 m_cursor[CURSOR_MOUSE] = NO_CURSOR;
158 m_cursor[CURSOR_TUTORIAL] = NO_CURSOR;
159 m_cursor[CURSOR_MAP] = NO_CURSOR;
161 // Initially, switch to CRT.
162 cgotoxy(1, 1, GOTO_CRT);
167 string TilesFramework::get_message()
172 void TilesFramework::write_message(const char *format, ...)
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);
185 m_msg_buf.append(buf);
188 void TilesFramework::finish_message()
190 if (m_msg_buf.size() == 0)
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)
198 int fragment_size = data_end - fragment_start;
199 if (fragment_size > m_max_msg_size)
200 fragment_size = m_max_msg_size;
202 for (unsigned int i = 0; i < m_dest_addrs.size(); ++i)
206 while (sent < fragment_size)
208 ssize_t retval = sendto(m_sock, fragment_start + sent,
209 fragment_size - sent, 0, (sockaddr*) &m_dest_addrs[i],
210 sizeof(sockaddr_un));
213 const char *errmsg = retval == 0 ? "No bytes sent"
216 die("Socket write error: %s", errmsg);
218 if (retval == 0 || errno == ENOBUFS || errno == EWOULDBLOCK
219 || errno == EINTR || errno == EAGAIN)
221 // Wait for half a second at first (up to five), then
223 usleep(retries <= 10 ? 5000 * 1000 : 500 * 1000);
225 else if (errno == ECONNREFUSED || errno == ENOENT)
227 // the other side is dead
228 m_dest_addrs.erase(m_dest_addrs.begin() + i);
233 die("Socket write error: %s", errmsg);
240 fragment_start += fragment_size;
245 void TilesFramework::send_message(const char *format, ...)
251 va_start(argp, format);
252 if ((len = vsnprintf(buf, sizeof(buf), format, argp)) >= (int)sizeof(buf)
256 die("Webtiles message format error! (%s)", format);
258 die("Webtiles message too long! (%d)", len);
262 m_msg_buf.append(buf);
267 void TilesFramework::flush_messages()
269 send_message("*{\"msg\":\"flush_messages\"}");
272 void TilesFramework::_await_connection()
274 while (m_dest_addrs.size() == 0)
275 _receive_control_message();
278 wint_t TilesFramework::_receive_control_message()
280 char buf[4096]; // Should be enough for client->server messages
282 socklen_t srcaddr_len;
284 srcaddr_len = sizeof(srcaddr);
286 int len = recvfrom(m_sock, buf, sizeof(buf),
288 (sockaddr *) &srcaddr, &srcaddr_len);
291 die("Socket read error: %s", strerror(errno));
293 string data(buf, len);
296 return _handle_control_message(srcaddr, data);
298 catch (JsonWrapper::MalformedException&)
300 dprf("Malformed control message!");
305 wint_t TilesFramework::_handle_control_message(sockaddr_un addr, string data)
307 JsonWrapper obj = json_decode(data.c_str());
308 obj.check(JSON_OBJECT);
310 JsonWrapper msg = json_find_member(obj.node, "msg");
311 msg.check(JSON_STRING);
312 string msgtype(msg->string_);
316 if (msgtype == "attach")
318 JsonWrapper primary = json_find_member(obj.node, "primary");
319 primary.check(JSON_BOOL);
321 m_dest_addrs.push_back(addr);
322 m_controlled_from_web = primary->bool_;
324 else if (msgtype == "key")
326 JsonWrapper keycode = json_find_member(obj.node, "keycode");
327 keycode.check(JSON_NUMBER);
329 c = (int) keycode->number_;
331 else if (msgtype == "spectator_joined")
337 else if (msgtype == "menu_scroll")
339 JsonWrapper first = json_find_member(obj.node, "first");
340 first.check(JSON_NUMBER);
341 // last visible item is sent too, but currently unused
343 if (!m_menu_stack.empty() && m_menu_stack.back().menu != NULL)
344 m_menu_stack.back().menu->webtiles_scroll((int) first->number_);
346 else if (msgtype == "*request_menu_range")
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);
353 if (!m_menu_stack.empty() && m_menu_stack.back().menu != NULL)
355 m_menu_stack.back().menu->webtiles_handle_item_request((int) start->number_,
359 else if (msgtype == "note")
361 JsonWrapper content = json_find_member(obj.node, "content");
362 content.check(JSON_STRING);
364 if (Options.note_chat_messages)
365 take_note(Note(NOTE_MESSAGE, MSGCH_PLAIN, 0, content->string_));
371 bool TilesFramework::await_input(wint_t& c, bool block)
382 FD_SET(STDIN_FILENO, &fds);
383 FD_SET(m_sock, &fds);
387 tiles.flush_messages();
388 result = select(maxfd + 1, &fds, NULL, NULL, NULL);
396 result = select(maxfd + 1, &fds, NULL, NULL, &timeout);
399 while (result == -1 && errno == EINTR);
405 if (FD_ISSET(m_sock, &fds))
407 c = _receive_control_message();
413 if (FD_ISSET(STDIN_FILENO, &fds))
419 else if (errno == EBADF)
421 // This probably means that stdin got closed because of a
422 // SIGHUP. We'll just return.
427 die("select error: %s", strerror(errno));
431 void TilesFramework::dump()
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)
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);
443 void TilesFramework::send_exit_reason(const string& type, const string& message)
446 write_message("{\"msg\":\"exit_reason\",\"type\":\"");
447 write_message_escaped(type);
448 if (!message.empty())
450 write_message("\",\"message\":\"");
451 write_message_escaped(message);
453 write_message("\"}");
457 void TilesFramework::send_dump_info(const string& type, const string& filename)
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("\"}");
468 void TilesFramework::_send_version()
471 // The star signals a message to the server
472 send_message("*{\"msg\":\"client_path\",\"path\":\"%s\",\"version\":\"%s\"}", WEB_DIR_PATH, Version::Long);
475 string title = CRAWL " " + string(Version::Long);
476 send_message("{\"msg\":\"version\",\"text\":\"%s\"}", title.c_str());
479 void TilesFramework::_send_options()
482 json_write_string("msg", "options");
483 Options.write_webtiles_options("options");
488 void TilesFramework::push_menu(Menu* m)
492 m_menu_stack.push_back(mi);
493 m->webtiles_write_menu();
494 tiles.finish_message();
497 void TilesFramework::push_crt_menu(string tag)
502 m_menu_stack.push_back(mi);
505 json_write_string("msg", "menu");
506 json_write_string("type", "crt");
507 json_write_string("tag", tag);
512 bool TilesFramework::is_in_crt_menu()
514 return is_in_menu(NULL);
517 bool TilesFramework::is_in_menu(Menu* m)
519 return !m_menu_stack.empty() && m_menu_stack.back().menu == m;
522 void TilesFramework::pop_menu()
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\"}");
530 void TilesFramework::close_all_menus()
532 while (m_menu_stack.size())
536 static void _send_ui_state(WebtilesUIState state)
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();
545 void TilesFramework::set_ui_state(WebtilesUIState state)
547 if (m_ui_state == state) return;
552 void TilesFramework::update_input_mode(mouse_mode mode)
557 json_write_string("msg", "input_mode");
558 json_write_int("mode", mode);
563 static bool _update_string(bool force, string& current,
568 if (force || current != next)
570 tiles.json_write_string(name, next);
579 template<class T> static bool _update_int(bool force, T& current, T next,
583 if (force || current != next)
585 tiles.json_write_int(name, next);
594 static bool _update_statuses(player_info& c)
596 bool changed = false;
597 unsigned int counter = 0;
599 for (unsigned int status = 0; status <= STATUS_LAST_STATUS; ++status)
601 if (status == DUR_CONDENSATION_SHIELD || status == DUR_DIVINE_SHIELD)
603 if (!you.duration[status])
605 inf.short_text = "shielded";
607 else if (status == DUR_ICEMAIL_DEPLETED)
609 if (you.duration[status] <= ICEMAIL_TIME / ICEMAIL_MAX)
611 inf.short_text = "icemail depleted";
613 else if (!fill_status_info(status, &inf))
616 if (!inf.light_text.empty() || !inf.short_text.empty())
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)
631 c.status.resize(counter + 1);
632 c.status[counter] = inf;
638 if (c.status.size() != counter)
642 c.status.resize(counter);
648 player_info::player_info()
650 for (unsigned int i = 0; i < NUM_EQUIP; ++i)
652 position = coord_def(-1, -1);
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.
663 void TilesFramework::_send_player(bool force_full)
665 player_info& c = m_current_player_info;
668 json_write_string("msg", "player");
669 json_treat_as_empty();
671 _update_string(force_full, c.name, you.your_name, "name");
672 _update_string(force_full, c.job_title, filtered_lang(player_title()),
674 _update_int(force_full, c.wizard, you.wizard, "wizard");
675 _update_string(force_full, c.species, species_name(you.species),
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");
685 if (you_worship(GOD_XOM))
687 if (!you.gift_timeout)
689 else if (you.gift_timeout == 1)
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
699 _update_int(force_full, c.piety_rank, prank, "piety_rank");
701 _update_int(force_full, c.form, (uint8_t) you.form, "form");
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
710 _update_int(force_full, c.real_hp_max, max_max_hp, "real_hp_max");
712 if (you.species != SP_DJINNI)
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");
718 if (you.species == SP_DJINNI)
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;
725 else if (contam >= 16000)
727 _update_int(force_full, c.contam, contam, "contam");
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");
734 _update_int(force_full, c.poison_survival, max(0, poison_survival()),
737 #if TAG_MAJOR_VERSION == 34
738 if (you.species == SP_LAVA_ORC)
739 _update_int(force_full, c.heat, temperature(), "heat");
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(),
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");
754 if (you.species == SP_FELID)
756 _update_int(force_full, c.lives, you.lives, "lives");
757 _update_int(force_full, c.deaths, you.deaths, "deaths");
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");
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
768 _update_int(force_full, c.elapsed_time, you.elapsed_time, "time");
769 _update_int(force_full, c.num_turns, you.num_turns, "turn");
772 const PlaceInfo& place = you.get_place_info();
773 string short_name = branches[place.branch].shortname;
775 if (brdepth[place.branch] == 1)
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))
784 short_name = article_a(short_name);
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");
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)
795 json_open_object("pos");
796 json_write_int("x", pos.x);
797 json_write_int("y", pos.y);
802 if (force_full || _update_statuses(c))
804 json_open_array("status");
805 for (unsigned int i = 0; i < c.status.size(); ++i)
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);
819 json_open_object("inv");
820 for (unsigned int i = 0; i < ENDOFPACK; ++i)
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);
826 json_close_object(true);
828 json_open_object("equip");
829 for (unsigned int i = 0; i < NUM_EQUIP; ++i)
831 _update_int(force_full, c.equip[i], you.equip[i],
832 make_stringf("%d", i));
834 json_close_object(true);
836 _update_int(force_full, c.quiver_item,
837 (int8_t) you.m_quiver->get_fire_item(), "quiver_item");
839 _update_string(force_full, c.unarmed_attack,
840 you.unarmed_attack_name(), "unarmed_attack");
842 json_close_object(true);
847 void TilesFramework::_send_item(item_info& current, const item_info& next,
850 bool changed = false;
851 bool defined = next.defined();
853 if (force_full || current.base_type != next.base_type)
856 json_write_int("base_type", next.base_type);
859 changed |= _update_int(force_full, current.quantity, next.quantity,
865 return; // For undefined items, only send base_type and quantity
867 else if (!current.defined())
868 force_full = true; // if the item was undefined before, send everything
870 changed |= _update_int(force_full, current.sub_type, next.sub_type,
872 changed |= _update_int(force_full, current.plus, next.plus,
874 changed |= _update_int(force_full, current.plus2, next.plus2,
876 changed |= _update_int(force_full, current.flags, next.flags,
878 changed |= _update_string(force_full, current.inscription,
879 next.inscription, "inscription", false);
883 changed |= (current.special != next.special);
886 if (changed && defined)
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);
892 const string prefix = item_prefix(next);
893 const int prefcol = menu_colour(next.name(DESC_INVENTORY), prefix);
895 json_write_int("col", macro_colour(prefcol));
898 const string current_prefix = item_prefix(current);
899 const int current_prefcol = menu_colour(current.name(DESC_INVENTORY), current_prefix);
901 if (current_prefcol != prefcol)
902 json_write_int("col", macro_colour(prefcol));
905 tileidx_t tile = tileidx_item(next);
906 if (force_full || tileidx_item(current) != tile)
908 json_open_array("tile");
909 tileidx_t base_tile = tileidx_known_base_item(tile);
911 json_write_int(base_tile);
912 json_write_int(tile);
920 static void _send_doll(const dolls_data &doll, bool submerged, bool ghost)
922 // Ordered from back to front.
923 int p_order[TILEP_PART_MAX] =
945 int flags[TILEP_PART_MAX];
946 tilep_calc_flags(doll, flags);
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)
951 p_order[7] = TILEP_PART_BOOTS;
952 p_order[6] = TILEP_PART_LEG;
955 // Special case bardings from being cut off.
956 const bool is_naga = is_player_tile(doll.parts[TILEP_PART_BASE],
959 if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_NAGA_BARDING
960 && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_NAGA_BARDING_RED)
962 flags[TILEP_PART_BOOTS] = is_naga ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
965 const bool is_cent = is_player_tile(doll.parts[TILEP_PART_BASE],
968 if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_CENTAUR_BARDING
969 && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_CENTAUR_BARDING_RED)
971 flags[TILEP_PART_BOOTS] = is_cent ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
974 tiles.json_open_array("doll");
976 for (int i = 0; i < TILEP_PART_MAX; ++i)
980 if (!doll.parts[p] || flags[p] == TILEP_FLAG_HIDE)
983 if (p == TILEP_PART_SHADOW && (submerged || ghost))
988 if (flags[p] == TILEP_FLAG_CUT_CENTAUR
989 || flags[p] == TILEP_FLAG_CUT_NAGA)
994 tiles.json_write_comma();
995 tiles.write_message("[%u,%d]", (unsigned int) doll.parts[p], ymax);
997 tiles.json_close_array();
1000 static void _send_mcache(mcache_entry *entry, bool submerged,
1001 bool send_doll = true)
1003 bool trans = entry->transparent();
1004 if (trans && send_doll)
1005 tiles.json_write_int("trans", 1);
1007 const dolls_data *doll = entry->doll();
1011 _send_doll(*doll, submerged, trans);
1014 tiles.json_write_comma();
1015 tiles.write_message("\"doll\":[]");
1019 tiles.json_open_array("mcache");
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++)
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);
1030 tiles.json_close_array();
1033 static bool _in_water(const packed_cell &cell)
1035 return (cell.bg & TILE_FLAG_WATER) && !(cell.fg & TILE_FLAG_FLYING);
1038 static bool _needs_flavour(const packed_cell &cell)
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)
1046 return true; // Needs flv.special
1051 static inline unsigned _get_brand(int col)
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
1064 static inline void _write_tileidx(tileidx_t t)
1066 // JS can only handle signed ints
1067 const int lo = t & 0xFFFFFFFF;
1068 const int hi = t >> 32;
1070 tiles.write_message("%d", lo);
1072 tiles.write_message("[%d,%d]", lo, hi);
1075 void TilesFramework::_send_cell(const coord_def &gc,
1076 const screen_cell_t ¤t_sc, const screen_cell_t &next_sc,
1077 const map_cell ¤t_mc, const map_cell &next_mc,
1078 map<uint32_t, coord_def>& new_monster_locs,
1081 if (current_mc.feat() != next_mc.feat())
1082 json_write_int("f", next_mc.feat());
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");
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);
1094 ucs_t glyph = next_sc.glyph;
1095 if (current_sc.glyph != glyph)
1098 buf[wctoutf8(buf, glyph)] = 0;
1099 json_write_string("g", buf);
1101 if ((current_sc.colour != next_sc.colour
1102 || current_sc.glyph == ' ') && glyph != ' ')
1104 int col = next_sc.colour;
1105 col = (_get_brand(col) << 4) | macro_colour(col & 0xF);
1106 json_write_int("col", col);
1109 json_open_object("t");
1112 const packed_cell &next_pc = next_sc.tile;
1113 const packed_cell ¤t_pc = current_sc.tile;
1115 const tileidx_t fg_idx = next_pc.fg & TILE_FLAG_MASK;
1117 const bool in_water = _in_water(next_pc);
1118 bool fg_changed = false;
1120 if (next_pc.fg != current_pc.fg)
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));
1130 if (next_pc.bg != current_pc.bg)
1132 json_write_name("bg");
1133 _write_tileidx(next_pc.bg);
1136 if (next_pc.cloud != current_pc.cloud)
1138 json_write_name("cloud");
1139 _write_tileidx(next_pc.cloud);
1142 if (next_pc.is_bloody != current_pc.is_bloody)
1143 json_write_bool("bloody", next_pc.is_bloody);
1145 if (next_pc.old_blood != current_pc.old_blood)
1146 json_write_bool("old_blood", next_pc.old_blood);
1148 if (next_pc.is_silenced != current_pc.is_silenced)
1149 json_write_bool("silenced", next_pc.is_silenced);
1151 if (next_pc.halo != current_pc.halo)
1152 json_write_int("halo", next_pc.halo);
1154 if (next_pc.is_moldy != current_pc.is_moldy)
1155 json_write_bool("moldy", next_pc.is_moldy);
1157 if (next_pc.glowing_mold != current_pc.glowing_mold)
1158 json_write_bool("glowing_mold", next_pc.glowing_mold);
1160 if (next_pc.is_sanctuary != current_pc.is_sanctuary)
1161 json_write_bool("sanctuary", next_pc.is_sanctuary);
1163 if (next_pc.is_liquefied != current_pc.is_liquefied)
1164 json_write_bool("liquefied", next_pc.is_liquefied);
1166 if (next_pc.orb_glow != current_pc.orb_glow)
1167 json_write_int("orb_glow", next_pc.orb_glow);
1169 if (next_pc.quad_glow != current_pc.quad_glow)
1170 json_write_bool("quad_glow", next_pc.quad_glow);
1172 if (next_pc.disjunct != current_pc.disjunct)
1173 json_write_bool("disjunct", next_pc.disjunct);
1175 if (next_pc.mangrove_water != current_pc.mangrove_water)
1176 json_write_bool("mangrove_water", next_pc.mangrove_water);
1178 if (next_pc.blood_rotation != current_pc.blood_rotation)
1179 json_write_int("blood_rotation", next_pc.blood_rotation);
1181 if (next_pc.travel_trail != current_pc.travel_trail)
1182 json_write_int("travel_trail", next_pc.travel_trail);
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);
1189 if (next_pc.gold_aura != current_pc.gold_aura)
1190 json_write_int("gold_aura", next_pc.gold_aura);
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)
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();
1205 if (fg_idx >= TILEP_MCACHE_START)
1209 mcache_entry *entry = mcache.get(fg_idx);
1211 _send_mcache(entry, in_water);
1215 write_message("\"doll\":[[%d,%d]]", TILEP_MONS_UNKNOWN, TILE_Y);
1219 else if (fg_idx == TILEP_PLAYER)
1221 bool player_doll_changed = false;
1222 dolls_data result = player_doll;
1223 fill_doll_equipment(result);
1224 if (result != last_player_doll)
1226 player_doll_changed = true;
1227 last_player_doll = result;
1229 if (fg_changed || player_doll_changed)
1231 _send_doll(last_player_doll, in_water, false);
1232 if (Options.tile_use_monster != MONS_0)
1234 monster_info minfo(MONS_PLAYER, MONS_PLAYER);
1235 minfo.props["monster_tile"] =
1236 short(last_player_doll.parts[TILEP_PART_BASE]);
1238 if (you.slot_item(EQ_WEAPON))
1240 item = new item_def(get_item_info(*you.slot_item(EQ_WEAPON)));
1241 minfo.inv[MSLOT_WEAPON].reset(item);
1243 if (you.slot_item(EQ_SHIELD))
1245 item = new item_def(get_item_info(*you.slot_item(EQ_SHIELD)));
1246 minfo.inv[MSLOT_SHIELD].reset(item);
1248 tileidx_t mcache_idx = mcache.register_monster(minfo);
1249 mcache_entry *entry = mcache.get(mcache_idx);
1251 _send_mcache(entry, in_water, false);
1255 else if (fg_idx >= TILE_MAIN_MAX)
1260 write_message("\"doll\":[[%u,%d]]", (unsigned int) fg_idx, TILE_Y);
1264 bool overlays_changed = false;
1266 if (next_pc.num_dngn_overlay != current_pc.num_dngn_overlay)
1267 overlays_changed = true;
1270 for (int i = 0; i < next_pc.num_dngn_overlay; i++)
1272 if (next_pc.dngn_overlay[i] != current_pc.dngn_overlay[i])
1274 overlays_changed = true;
1280 if (overlays_changed)
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]);
1288 json_close_object(true);
1291 void TilesFramework::_send_cursor(cursor_type type)
1293 if (m_cursor[type] == NO_CURSOR)
1294 send_message("{\"msg\":\"cursor\",\"id\":%d}", type);
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);
1305 void TilesFramework::_mcache_ref(bool inc)
1307 for (int y = 0; y < GYM; y++)
1308 for (int x = 0; x < GXM; x++)
1312 int fg_idx = m_current_view(gc).tile.fg & TILE_FLAG_MASK;
1313 if (fg_idx >= TILEP_MCACHE_START)
1315 mcache_entry *entry = mcache.get(fg_idx);
1327 void TilesFramework::_send_map(bool force_full)
1329 map<uint32_t, coord_def> new_monster_locs;
1331 force_full = force_full || m_need_full_map;
1332 m_need_full_map = false;
1335 json_write_string("msg", "map");
1336 json_treat_as_empty();
1339 json_write_bool("clear", true);
1341 if (force_full || you.on_current_level != m_player_on_level)
1343 json_write_bool("player_on_level", you.on_current_level);
1344 m_player_on_level = you.on_current_level;
1347 if (force_full || m_current_gc != m_next_gc)
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;
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;
1364 coord_def last_gc(0, 0);
1365 bool send_gc = true;
1367 json_open_array("cells");
1368 for (int y = 0; y < GYM; y++)
1369 for (int x = 0; x < GXM; x++)
1373 if (!is_dirty(gc) && !force_full)
1376 if (cell_needs_redraw(gc))
1378 screen_cell_t *cell = &m_next_view(gc);
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));
1387 if (m_origin.equals(-1, -1))
1392 || last_gc.x + 1 != gc.x
1393 || last_gc.y != gc.y)
1395 json_write_int("x", x - m_origin.x);
1396 json_write_int("y", y - m_origin.y);
1397 json_treat_as_empty();
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);
1407 mc, env.map_knowledge(gc),
1408 new_monster_locs, force_full);
1410 if (!json_is_empty())
1415 json_close_object(true);
1417 json_close_array(true);
1419 json_close_object(true);
1424 _send_cursor(CURSOR_MAP);
1426 if (m_mcache_ref_done)
1429 m_current_map_knowledge = env.map_knowledge;
1430 m_current_view = m_next_view;
1433 m_mcache_ref_done = true;
1435 m_monster_locs = new_monster_locs;
1438 void TilesFramework::_send_monster(const coord_def &gc, const monster_info* m,
1439 map<uint32_t, coord_def>& new_monster_locs,
1442 json_open_object("mon");
1445 json_write_int("id", m->client_id);
1446 json_treat_as_empty();
1447 new_monster_locs[m->client_id] = gc;
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())
1455 last = m_current_map_knowledge(gc).monsterinfo();
1457 if (last && last->client_id != m->client_id)
1458 json_treat_as_nonempty(); // Force sending at least the id
1462 last = m_current_map_knowledge(it->second).monsterinfo();
1464 if (it->second != gc)
1465 json_treat_as_nonempty(); // As above
1471 if (force_full || (last->full_name() != m->full_name()))
1472 json_write_string("name", m->full_name());
1474 if (force_full || (last->pluralised_name() != m->pluralised_name()))
1475 json_write_string("plural", m->pluralised_name());
1477 if (force_full || last->type != m->type)
1479 json_write_int("type", m->type);
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();
1489 if (force_full || last->attitude != m->attitude)
1490 json_write_int("att", m->attitude);
1492 if (force_full || last->base_type != m->base_type)
1493 json_write_int("btype", m->base_type);
1495 if (force_full || last->threat != m->threat)
1496 json_write_int("threat", m->threat);
1498 json_close_object(true);
1501 void TilesFramework::load_dungeon(const crawl_view_buffer &vbuf,
1502 const coord_def &gc)
1504 if (vbuf.size().equals(0, 0))
1507 m_view_loaded = true;
1509 if (m_ui_state == UI_CRT)
1510 set_ui_state(UI_NORMAL);
1512 m_next_flash_colour = you.flash_colour;
1513 if (m_next_flash_colour == BLACK)
1514 m_next_flash_colour = viewmap_flash_colour();
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++)
1520 if (x < 0 || x >= GXM || y < 0 || y >= GYM)
1523 if (!crawl_view.in_viewport_g(coord_def(x, y)))
1524 mark_for_redraw(coord_def(x, y));
1527 m_next_view_tl = view2grid(coord_def(1, 1));
1528 m_next_view_br = view2grid(crawl_view.viewsz);
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++)
1534 coord_def pos(x+1, y+1);
1535 coord_def grid = view2grid(pos);
1537 if (grid.x < 0 || grid.x >= GXM || grid.y < 0 || grid.y >= GYM)
1540 screen_cell_t *cell = &m_next_view(grid);
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));
1546 mark_clean(grid); // Remove redraw flag
1553 void TilesFramework::load_dungeon(const coord_def &cen)
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);
1562 crawl_view.calc_vlos();
1563 viewwindow(false, true);
1564 place_cursor(CURSOR_MAP, cen);
1567 void TilesFramework::resize()
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);
1574 Send everything a newly joined spectator needs
1576 void TilesFramework::_send_everything()
1582 _send_ui_state(m_ui_state);
1583 m_last_ui_state = m_ui_state;
1585 send_message("{\"msg\":\"flash\",\"col\":%d}", m_current_flash_colour);
1589 _send_cursor(CURSOR_MOUSE);
1590 _send_cursor(CURSOR_TUTORIAL);
1597 json_write_string("msg", "init_menus");
1598 json_open_array("menus");
1599 for (unsigned int i = 0; i < m_menu_stack.size(); ++i)
1601 if (m_menu_stack[i].menu)
1602 m_menu_stack[i].menu->webtiles_write_menu();
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();
1613 json_close_object();
1616 webtiles_send_last_messages();
1618 update_input_mode(mouse_control::current_mode());
1620 m_text_crt.send(true);
1621 m_text_menu.send(true);
1624 void TilesFramework::clrscr()
1627 m_text_menu.clear();
1634 void TilesFramework::cgotoxy(int x, int y, GotoRegion region)
1644 m_print_area = NULL;
1647 set_ui_state(UI_CRT);
1648 m_print_area = &m_text_crt;
1651 m_print_area = &m_text_menu;
1657 set_ui_state(UI_NORMAL);
1658 m_print_area = NULL;
1661 m_print_area = NULL;
1664 m_cursor_region = region;
1667 void TilesFramework::redraw()
1669 if (!has_receivers())
1671 if (m_mcache_ref_done)
1674 m_mcache_ref_done = false;
1679 if (m_last_ui_state != m_ui_state)
1681 _send_ui_state(m_ui_state);
1682 m_last_ui_state = m_ui_state;
1689 webtiles_send_messages();
1691 if (m_need_redraw && m_view_loaded)
1693 if (m_current_flash_colour != m_next_flash_colour)
1695 send_message("{\"msg\":\"flash\",\"col\":%d}",
1696 m_next_flash_colour);
1697 m_current_flash_colour = m_next_flash_colour;
1702 m_need_redraw = false;
1703 m_last_tick_redraw = get_milliseconds();
1706 void TilesFramework::update_minimap(const coord_def& gc)
1708 if (gc.x < 0 || gc.x >= GXM || gc.y < 0 || gc.y >= GYM)
1711 mark_for_redraw(gc);
1714 void TilesFramework::clear_minimap()
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;
1722 void TilesFramework::update_minimap_bounds()
1726 void TilesFramework::update_tabs()
1730 void TilesFramework::place_cursor(cursor_type type, const coord_def &gc)
1732 // This is mainly copied from DungeonRegion::place_cursor.
1733 coord_def result = gc;
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)
1741 coord_def delta = gc - you.pos();
1743 int ax = abs(delta.x);
1744 int ay = abs(delta.y);
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);
1757 if (m_cursor[type] != result)
1759 m_cursor[type] = result;
1760 if (type == CURSOR_MOUSE)
1761 m_last_clicked_grid = coord_def();
1763 // if map is going to be updated, send the cursor after that
1764 if (type == CURSOR_MAP && m_need_full_map)
1771 void TilesFramework::clear_text_tags(text_tag_type type)
1775 void TilesFramework::add_text_tag(text_tag_type type, const string &tag,
1776 const coord_def &gc)
1780 void TilesFramework::add_text_tag(text_tag_type type, const monster_info& mon)
1784 const coord_def &TilesFramework::get_cursor() const
1786 return m_cursor[CURSOR_MOUSE];
1789 void TilesFramework::add_overlay(const coord_def &gc, tileidx_t idx)
1791 if (idx >= TILE_MAIN_MAX)
1794 m_has_overlays = true;
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);
1800 void TilesFramework::clear_overlays()
1803 send_message("{\"msg\":\"clear_overlays\"}");
1805 m_has_overlays = false;
1808 void TilesFramework::set_need_redraw(unsigned int min_tick_delay)
1810 unsigned int ticks = (get_milliseconds() - m_last_tick_redraw);
1811 if (min_tick_delay && ticks <= min_tick_delay)
1814 m_need_redraw = true;
1817 bool TilesFramework::need_redraw() const
1819 return m_need_redraw;
1822 void TilesFramework::textcolour(int col)
1824 m_print_fg = col & 0xF;
1825 m_print_bg = (col >> 4) & 0xF;
1828 void TilesFramework::textbackground(int col)
1833 void TilesFramework::put_ucs_string(ucs_t *str)
1835 if (m_print_area == NULL)
1847 // TODO: Clear end of line?
1851 if (m_print_x >= m_print_area->mx)
1857 if (m_print_y < m_print_area->my)
1859 m_print_area->put_character(*str, m_print_fg, m_print_bg,
1860 m_print_x, m_print_y);
1870 void TilesFramework::clear_to_end_of_line()
1872 if (m_print_area == NULL || m_print_y >= m_print_area->my)
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);
1879 void TilesFramework::mark_for_redraw(const coord_def& gc)
1882 m_cells_needing_redraw[gc.y * GXM + gc.x] = true;
1885 void TilesFramework::mark_dirty(const coord_def& gc)
1887 m_dirty_cells[gc.y * GXM + gc.x] = true;
1890 void TilesFramework::mark_clean(const coord_def& gc)
1892 m_cells_needing_redraw[gc.y * GXM + gc.x] = false;
1893 m_dirty_cells[gc.y * GXM + gc.x] = false;
1896 bool TilesFramework::is_dirty(const coord_def& gc)
1898 return m_dirty_cells[gc.y * GXM + gc.x];
1901 bool TilesFramework::cell_needs_redraw(const coord_def& gc)
1903 return m_cells_needing_redraw[gc.y * GXM + gc.x];
1906 void TilesFramework::write_message_escaped(const string& s)
1908 m_msg_buf.reserve(m_msg_buf.size() + s.size());
1910 for (size_t i = 0; i < s.size(); ++i)
1912 unsigned char c = s[i];
1914 m_msg_buf.append("\\\"");
1916 m_msg_buf.append("\\\\");
1920 snprintf(buf, sizeof(buf), "\\u%04x", c);
1921 m_msg_buf.append(buf);
1924 m_msg_buf.append(1, c);
1928 void TilesFramework::json_open(const string& name, char opener, char type)
1930 m_json_stack.resize(m_json_stack.size() + 1);
1931 JsonFrame& fr = m_json_stack.back();
1932 fr.start = m_msg_buf.size();
1936 json_write_name(name);
1938 m_msg_buf.append(1, opener);
1940 fr.prefix_end = m_msg_buf.size();
1944 void TilesFramework::json_treat_as_empty()
1946 if (m_json_stack.empty())
1947 die("json error: empty stack");
1948 m_json_stack.back().prefix_end = m_msg_buf.size();
1951 void TilesFramework::json_treat_as_nonempty()
1953 if (m_json_stack.empty())
1954 die("json error: empty stack");
1955 m_json_stack.back().prefix_end = -1;
1958 bool TilesFramework::json_is_empty()
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();
1965 void TilesFramework::json_close(bool erase_if_empty, char type)
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");
1972 if (erase_if_empty && json_is_empty())
1973 m_msg_buf.resize(m_json_stack.back().start);
1975 m_msg_buf.append(1, type);
1977 m_json_stack.pop_back();
1980 void TilesFramework::json_open_object(const string& name)
1982 json_open(name, '{', '}');
1985 void TilesFramework::json_close_object(bool erase_if_empty)
1987 json_close(erase_if_empty, '}');
1990 void TilesFramework::json_open_array(const string& name)
1992 json_open(name, '[', ']');
1995 void TilesFramework::json_close_array(bool erase_if_empty)
1997 json_close(erase_if_empty, ']');
2000 void TilesFramework::json_write_comma()
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;
2008 void TilesFramework::json_write_name(const string& name)
2012 write_message("\"");
2013 write_message_escaped(name);
2014 write_message("\":");
2017 void TilesFramework::json_write_int(int value)
2021 write_message("%d", value);
2024 void TilesFramework::json_write_int(const string& name, int value)
2027 json_write_name(name);
2029 json_write_int(value);
2032 void TilesFramework::json_write_bool(bool value)
2037 write_message("true");
2039 write_message("false");
2042 void TilesFramework::json_write_bool(const string& name, bool value)
2045 json_write_name(name);
2047 json_write_bool(value);
2050 void TilesFramework::json_write_null()
2054 write_message("null");
2057 void TilesFramework::json_write_null(const string& name)
2060 json_write_name(name);
2065 void TilesFramework::json_write_string(const string& value)
2069 write_message("\"");
2070 write_message_escaped(value);
2071 write_message("\"");
2074 void TilesFramework::json_write_string(const string& name, const string& value)
2077 json_write_name(name);
2079 json_write_string(value);
2084 return tiles.is_controlled_from_web();