<button id="toggle_map_mode">toggle terrain/annotations/control view</button>
<button id="switch_to_admin">become admin</button>
<button id="switch_to_control_pw_type">change tile control password</button>
+<button id="switch_to_control_tile_type">change tiles control</button>
</div>
<h3>edit keybindings</h3> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
<ul>
<li>enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" />
<li>enter admin password (from play mode): <input id="key_switch_to_admin" type="text" value="A" />
<li>change tile control password (from play mode): <input id="key_switch_to_control_pw_type" type="text" value="C" />
+<li>change tiles control (from play mode): <input id="key_switch_to_control_tile_type" type="text" value="Q" />
<li>annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" />
<li>annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" />
<li>toggle terrain/annotations/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" />
'long': 'This mode is the first of two steps to change the password for a tile control character. First enter the tile control character for which you want to change the password!'
},
'control_pw_pw': {
- 'short': '',
+ 'short': 'change tile control password',
'long': 'This mode is the second of two steps to change the password for a tile control character. Enter the new password for the tile control character you chose.'
},
+ 'control_tile_type': {
+ 'short': 'change tiles control',
+ 'long': 'This mode is the first of two steps to change tile control areas on the map. First enter the tile control character you want to write.'
+ },
+ 'control_tile_draw': {
+ 'short': 'change tiles control',
+ 'long': 'This mode is the second of two steps to change tile control areas on the map. Move cursor around the map to draw selected tile control character'
+ },
'annotate': {
'short': 'annotation',
'long': 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.'
'long': 'This mode allows you to engage in chit-chat with other users. Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message. Lines that start with a "/" are used for commands like:'
},
'login': {
- 'short': '',
+ 'short': 'login',
'long': 'Pick your player name.'
},
'waiting_for_server': {
- 'short': '',
+ 'short': 'waiting for server response',
'long': 'Waiting for a server response.'
},
'post_login_wait': {
- 'short': '',
+ 'short': 'waiting for server response',
'long': 'Waiting for a server response.'
},
'password': {
- 'short': 'password input',
+ 'short': 'map edit password',
'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.'
},
'admin': {
}, 100);
},
refresh: function() {
- let pre_string = '';
+ function escapeHTML(str) {
+ return str.
+ replace(/&/g, '&').
+ replace(/</g, '<').
+ replace(/>/g, '>').
+ replace(/'/g, ''').
+ replace(/"/g, '"');
+ };
+ let pre_content = '';
for (let y = 0; y < this.rows; y++) {
let line = this.content[y].join('');
- pre_string += line + '\n';
+ let chunks = [];
+ if (y in tui.links) {
+ let start_x = 0;
+ for (let span of tui.links[y]) {
+ chunks.push(escapeHTML(line.slice(start_x, span[0])));
+ chunks.push('<a href="');
+ chunks.push(escapeHTML(span[2]));
+ chunks.push('">');
+ chunks.push(escapeHTML(line.slice(span[0], span[1])));
+ chunks.push('</a>');
+ start_x = span[1];
+ }
+ chunks.push(escapeHTML(line.slice(start_x)));
+ } else {
+ chunks = [escapeHTML(line)];
+ }
+ for (const chunk of chunks) {
+ pre_content += chunk;
+ }
+ pre_content += '\n';
}
- this.pre_el.textContent = pre_string;
+ this.pre_el.innerHTML = pre_content;
},
write: function(start_y, start_x, msg) {
for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
}
}
let tui = {
+ links: {},
log: [],
input_prompt: '> ',
input_lines: [],
mode_password: new Mode('password', true),
mode_admin: new Mode('admin', true),
mode_control_pw_pw: new Mode('control_pw_pw', true),
+ mode_control_tile_type: new Mode('control_tile_type',
+ false, false, false, true),
+ mode_control_tile_draw: new Mode('control_tile_draw'),
init: function() {
this.mode_play.available_modes = ["chat", "study", "edit",
"annotate", "portal",
"password", "admin",
- "control_pw_type"]
+ "control_pw_type",
+ "control_tile_type"]
this.mode_study.available_modes = ["chat", "play"]
+ this.mode_control_tile_draw.available_modes = ["play"]
this.mode = this.mode_waiting_for_server;
this.inputEl = document.getElementById("input");
this.inputEl.focus();
this.inputEl.focus();
this.map_mode = 'terrain';
this.mode = this['mode_' + mode_name];
- if (this.mode.shows_info && game.player_id in game.things) {
- explorer.position = game.things[game.player_id].position;
- explorer.query_info();
+ if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
+ explorer.position = game.things[game.player_id].position;
+ if (this.mode.shows_info) {
+ explorer.query_info();
+ } else if (this.mode.name == 'control_tile_draw') {
+ explorer.send_tile_control_command();
+ this.map_mode = 'control';
+ }
}
this.empty_input();
this.restore_input_values();
document.getElementById("switch_to_password").disabled = true;
document.getElementById("switch_to_admin").disabled = true;
document.getElementById("switch_to_control_pw_type").disabled = true;
+ document.getElementById("switch_to_control_tile_type").disabled = true;
document.getElementById("move_left").disabled = true;
document.getElementById("move_upleft").disabled = true;
document.getElementById("move_up").disabled = true;
document.getElementById("move_down").disabled = true;
document.getElementById("move_downright").disabled = true;
document.getElementById("move_right").disabled = true;
- if (this.mode.name == 'play' || this.mode.name == 'study') {
+ if (this.mode.name == 'play' || this.mode.name == 'study' || this.mode.name == 'control_tile_draw') {
document.getElementById("move_left").disabled = false;
document.getElementById("move_right").disabled = false;
if (game.map_geometry == 'Hex') {
document.getElementById("switch_to_password").disabled = false;
document.getElementById("switch_to_admin").disabled = false;
document.getElementById("switch_to_control_pw_type").disabled = false;
+ document.getElementById("switch_to_control_tile_type").disabled = false;
} else if (this.mode.name == 'study') {
document.getElementById("toggle_map_mode").disabled = false;
} else if (this.mode.is_single_char_entry) {
this.log_msg('@ enter admin password:')
} else if (this.mode.name == 'control_pw_pw') {
this.log_msg('@ enter tile control password for "' + this.tile_control_char + '":');
+ } else if (this.mode.name == 'control_pw_pw') {
+ this.log_msg('@ enter tile control password for "' + this.tile_control_char + '":');
}
this.full_refresh();
},
+ offset_links: function(offset, links) {
+ for (let y in links) {
+ let real_y = offset[0] + parseInt(y);
+ if (!this.links[real_y]) {
+ this.links[real_y] = [];
+ }
+ for (let link of links[y]) {
+ const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
+ this.links[real_y].push(offset_link);
+ }
+ }
+ },
restore_input_values: function() {
if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
let info = explorer.info_db[explorer.position];
}
},
recalc_input_lines: function() {
- this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
+ let _ = null;
+ [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
this.height_input = this.input_lines.length;
},
msg_into_lines_of_width: function(msg, width) {
- let chunk = "";
- let lines = [];
- for (let i = 0, x = 0; i < msg.length; i++, x++) {
- if (x >= width || msg[i] == "\n") {
- lines.push(chunk);
- chunk = "";
- x = 0;
+ function push_inner_link(y, end_x) {
+ if (!inner_links[y]) {
+ inner_links[y] = [];
+ };
+ inner_links[y].push([url_start_x, end_x, url]);
};
- if (msg[i] != "\n") {
- chunk += msg[i];
+ const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
+ let link_data = {};
+ let url_ends = [];
+ for (const match of matches) {
+ const url = match[0];
+ const url_start = match.index;
+ const url_end = match.index + match[0].length;
+ link_data[url_start] = url;
+ url_ends.push(url_end);
}
- }
- lines.push(chunk);
- return lines;
+ let url_start_x = 0;
+ let url = '';
+ let inner_links = {};
+ let in_link = false;
+ let chunk = "";
+ let lines = [];
+ for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
+ if (x >= width || msg[i] == "\n") {
+ if (in_link) {
+ push_inner_link(y, chunk.length);
+ url_start_x = 0;
+ };
+ lines.push(chunk);
+ chunk = "";
+ x = 0;
+ if (msg[i] == "\n") {
+ x -= 1;
+ };
+ y += 1;
+ };
+ if (msg[i] != "\n") {
+ chunk += msg[i];
+ };
+ if (i in link_data) {
+ url_start_x = x;
+ url = link_data[i];
+ in_link = true;
+ } else if (url_ends.includes(i)) {
+ push_inner_link(y, x);
+ in_link = false;
+ }
+ }
+ lines.push(chunk);
+ if (in_link) {
+ push_inner_link(lines.length - 1, chunk.length);
+ }
+ return [lines, inner_links];
},
log_msg: function(msg) {
this.log.push(msg);
used_positions.push(t.position.toString());
};
}
- if (tui.mode.shows_info) {
+ if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
}
let map_lines = []
if (this.mode.has_input_prompt) {
help = 'enter /help for help';
}
- terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
+ terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
},
draw_turn_line: function(n) {
terminal.write(1, this.window_width, 'TURN: ' + game.turn);
},
draw_history: function() {
let log_display_lines = [];
+ let log_links = {};
+ let y_offset_in_log = 0;
for (let line of this.log) {
- log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
+ let [new_lines, link_data] = this.msg_into_lines_of_width(line,
+ this.window_width)
+ log_display_lines = log_display_lines.concat(new_lines);
+ for (const y in link_data) {
+ const rel_y = y_offset_in_log + parseInt(y);
+ log_links[rel_y] = [];
+ for (let link of link_data[y]) {
+ log_links[rel_y].push(link);
+ }
+ }
+ y_offset_in_log += new_lines.length;
};
- for (let y = terminal.rows - 1 - this.height_input,
- i = log_display_lines.length - 1;
+ let i = log_display_lines.length - 1;
+ for (let y = terminal.rows - 1 - this.height_input;
y >= this.height_header && i >= 0;
y--, i--) {
terminal.write(y, this.window_width, log_display_lines[i]);
}
+ for (const key of Object.keys(log_links)) {
+ if (parseInt(key) <= i) {
+ delete log_links[key];
+ }
+ }
+ let offset = [terminal.rows - this.height_input - log_display_lines.length,
+ this.window_width];
+ this.offset_links(offset, log_links);
},
draw_info: function() {
- let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
- for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
- terminal.write(y, this.window_width, lines[i]);
- }
+ let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
+ this.window_width);
+ let offset = [this.height_header, this.window_width];
+ for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
+ terminal.write(y, offset[1], lines[i]);
+ }
+ this.offset_links(offset, link_data);
},
draw_input: function() {
if (this.mode.has_input_prompt) {
},
draw_help: function() {
let movement_keys_desc = Object.keys(this.movement_keys).join(',');
- let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
+ let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
if (this.mode.name == 'play') {
content += "Available actions:\n";
if (game.tasks.includes('MOVE')) {
start_x = this.window_width
}
terminal.drawBox(0, start_x, terminal.rows, this.window_width);
- let lines = this.msg_into_lines_of_width(content, this.window_width);
+ let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
terminal.write(y, start_x, lines[i]);
}
},
full_refresh: function() {
+ this.links = {};
terminal.drawBox(0, 0, terminal.rows, terminal.cols);
if (this.mode.is_intro) {
this.draw_history();
let target = game.move(this.position, direction);
if (target) {
this.position = target
- this.query_info();
+ if (tui.mode.shows_info) {
+ this.query_info();
+ } else if (tui.mode.name == 'control_tile_draw') {
+ this.send_tile_control_command();
+ }
} else {
terminal.blink_screen();
};
msg = " "; // triggers portal deletion
}
server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
+ },
+ send_tile_control_command: function() {
+ server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
}
}
} else if (tui.mode.name == 'control_pw_type' && tui.inputEl.value.length > 0) {
tui.tile_control_char = tui.inputEl.value[0];
tui.switch_mode('control_pw_pw');
+ } else if (tui.mode.name == 'control_tile_type' && tui.inputEl.value.length > 0) {
+ tui.tile_control_char = tui.inputEl.value[0];
+ tui.switch_mode('control_tile_draw');
}
tui.full_refresh();
}, false);
} else if (tui.mode.name == 'study') {
if (tui.mode.mode_switch_on_key(event)) {
null;
- } else if (event.key == tui.keys.switch_to_play) {
- tui.switch_mode('play');
} else if (event.key in tui.movement_keys) {
explorer.move(tui.movement_keys[event.key]);
} else if (event.key == tui.keys.toggle_map_mode) {
tui.map_mode = 'terrain';
}
};
+ } else if (tui.mode.name == 'control_tile_draw') {
+ if (tui.mode.mode_switch_on_key(event)) {
+ null;
+ } else if (event.key in tui.movement_keys) {
+ explorer.move(tui.movement_keys[event.key]);
+ };
}
tui.full_refresh();
}, false);
tui.switch_mode('control_pw_type');
tui.full_refresh();
};
+document.getElementById("switch_to_control_tile_type").onclick = function() {
+ tui.switch_mode('control_tile_type');
+ tui.full_refresh();
+};
document.getElementById("toggle_map_mode").onclick = function() {
if (tui.map_mode == 'terrain') {
tui.map_mode = 'annotations';