Release 0.9.0
This commit is contained in:
56
bot/modules/locations/templates/jinja2_macros.html
Normal file
56
bot/modules/locations/templates/jinja2_macros.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{%- macro construct_toggle_link(bool, active_text, deactivate_event, inactive_text, activate_event) -%}
|
||||
{%- set bool = bool|default(false) -%}
|
||||
{%- set active_text = active_text|default(none) -%}
|
||||
{%- set deactivate_event = deactivate_event|default(none) -%}
|
||||
{%- set inactive_text = inactive_text|default(none) -%}
|
||||
{%- set activate_event = activate_event|default(none) -%}
|
||||
{%- if bool == true -%}
|
||||
{%- if deactivate_event != none and activate_event != none -%}
|
||||
<span class="active"><a href="#" onclick="window.socket.emit('{{ deactivate_event[0] }}', {{ deactivate_event[1] }}); return false;">{{ active_text }}</a></span>
|
||||
{%- elif deactivate_event != none and activate_event == none -%}
|
||||
<span class="active"><a href="#" onclick="window.socket.emit('{{ deactivate_event[0] }}', {{ deactivate_event[1] }}); return false;">{{ active_text }}</a></span>
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if deactivate_event != none and activate_event != none -%}
|
||||
<span class="inactive"><a href="#" onclick="window.socket.emit('{{ activate_event[0] }}', {{ activate_event[1] }}); return false;">{{ inactive_text }}</a></span>
|
||||
{%- elif deactivate_event != none and activate_event == none -%}
|
||||
<span class="inactive"><a href="#" onclick="window.socket.emit('{{ deactivate_event[0] }}', {{ deactivate_event[1] }}); return false;">{{ active_text }}</a></span>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro construct_view_menu(views, current_view, module_name, steamid, default_view='frontend') -%}
|
||||
{#
|
||||
Dynamically construct a navigation menu for widget views.
|
||||
|
||||
Parameters:
|
||||
views: Dict of view configurations
|
||||
Example: {
|
||||
'frontend': {'label_active': 'back', 'label_inactive': 'main', 'action': 'show_frontend'},
|
||||
'options': {'label_active': 'back', 'label_inactive': 'options', 'action': 'show_options'}
|
||||
}
|
||||
current_view: Current active view name (string)
|
||||
module_name: Module name for socket.io event (e.g., 'locations')
|
||||
steamid: User's steamid for action parameters
|
||||
default_view: View to return to when deactivating (default: 'frontend')
|
||||
#}
|
||||
{%- for view_id, config in views.items() -%}
|
||||
{%- if config.get('include_in_menu', True) -%}
|
||||
{%- set is_active = (current_view == view_id) -%}
|
||||
{%- set label_active = config.get('label_active', config.get('label', 'back')) -%}
|
||||
{%- set label_inactive = config.get('label_inactive', config.get('label', view_id)) -%}
|
||||
{%- set action = config.get('action', 'show_' ~ view_id) -%}
|
||||
{%- set default_action = config.get('default_action', 'show_' ~ default_view) -%}
|
||||
|
||||
<div>
|
||||
{{ construct_toggle_link(
|
||||
is_active,
|
||||
label_active,
|
||||
['widget_event', [module_name, ['toggle_' ~ module_name ~ '_widget_view', {'steamid': steamid, 'action': default_action}]]],
|
||||
label_inactive,
|
||||
['widget_event', [module_name, ['toggle_' ~ module_name ~ '_widget_view', {'steamid': steamid, 'action': action}]]]
|
||||
)}}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endmacro -%}
|
||||
@@ -0,0 +1,12 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<span id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_control_edit_link" class="toggle_button">
|
||||
{{- construct_toggle_link(
|
||||
True,
|
||||
"edit", ['widget_event', ['locations', ['toggle_locations_widget_view', {
|
||||
"dom_element_owner": location.owner,
|
||||
"dom_element_identifier": location.identifier,
|
||||
"dom_element_origin": location.dataset_original,
|
||||
"action": "edit_location_entry"
|
||||
}]]]
|
||||
) -}}
|
||||
</span>
|
||||
@@ -0,0 +1,18 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<span id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_control_enabled_link" class="toggle_button">
|
||||
{{- construct_toggle_link(
|
||||
location.is_enabled,
|
||||
"enabled", ['widget_event', ['locations', ['toggle_enabled_flag', {
|
||||
"dom_element_owner": location.owner,
|
||||
"dom_element_identifier": location.identifier,
|
||||
"dom_element_origin": location.dataset_original,
|
||||
"action": "disable_location_entry"
|
||||
}]]],
|
||||
"enabled", ['widget_event', ['locations', ['toggle_enabled_flag', {
|
||||
"dom_element_owner": location.owner,
|
||||
"dom_element_identifier": location.identifier,
|
||||
"dom_element_origin": location.dataset_original,
|
||||
"action": "enable_location_entry"
|
||||
}]]]
|
||||
) -}}
|
||||
</span>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="bottom" id="current_player_pos">
|
||||
<span class="info">
|
||||
<div>
|
||||
<span>you are here:</span>
|
||||
<div>x: <span id="location_coordinates_x">{{ pos_x }}</span></div>
|
||||
<div>y: <span id="location_coordinates_y">{{ pos_y }}</span></div>
|
||||
<div>z: <span id="location_coordinates_z">{{ pos_z }}</span></div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<div>
|
||||
{{ construct_toggle_link(
|
||||
create_new_view_toggle,
|
||||
"create new location", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_create_new"}]]],
|
||||
"back", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_frontend"}]]]
|
||||
)}}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<div>
|
||||
{{ construct_toggle_link(
|
||||
map_view_toggle,
|
||||
"hide map", ['widget_event', ['locations', ['toggle_locations_widget_view', {
|
||||
"action": 'show_frontend'
|
||||
}]]],
|
||||
"show map", ['widget_event', ['locations', ['toggle_locations_widget_view', {
|
||||
"action": 'show_map'
|
||||
}]]]
|
||||
)}}
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<div>
|
||||
{{ construct_toggle_link(
|
||||
options_view_toggle,
|
||||
"options", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_options"}]]],
|
||||
"back", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_frontend"}]]]
|
||||
)}}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
<div>
|
||||
{{ construct_toggle_link(
|
||||
special_locations_view_toggle,
|
||||
"special locations", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_special_locations"}]]],
|
||||
"back", ['widget_event', ['locations', ['toggle_locations_widget_view', {'steamid': steamid, "action": "show_frontend"}]]]
|
||||
)}}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<div id="locations_widget_options_toggle" class="pull_out right">
|
||||
{{ control_switch_options_view }}
|
||||
{{ control_switch_create_new_view }}
|
||||
{{ control_switch_special_locations_view }}
|
||||
{{ control_switch_map_view }}
|
||||
{{ control_player_location_view }}
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
{%- from 'jinja2_macros.html' import construct_view_menu with context -%}
|
||||
<div id="locations_widget_options_toggle" class="pull_out right">
|
||||
{{ construct_view_menu(
|
||||
views=views,
|
||||
current_view=current_view,
|
||||
module_name='locations',
|
||||
steamid=steamid,
|
||||
default_view='frontend'
|
||||
)}}
|
||||
{{ control_player_location_view }}
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<div>{{ action_delete_button }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,9 @@
|
||||
<tr>
|
||||
<th>*</th>
|
||||
<th onclick="window.sorting(this, location_table, 1)">actions</th>
|
||||
<th onclick="window.sorting(this, location_table, 2)">name</th>
|
||||
<th onclick="window.sorting(this, location_table, 3)">owner name</th>
|
||||
<th onclick="window.sorting(this, location_table, 4)">identifier</th>
|
||||
<th onclick="window.sorting(this, location_table, 5)">coordinates</th>
|
||||
<th onclick="window.sorting(this, location_table, 6)">last changed</th>
|
||||
</tr>
|
||||
@@ -0,0 +1,18 @@
|
||||
{%- from 'jinja2_macros.html' import construct_toggle_link with context -%}
|
||||
{%- set location_is_enabled = location.is_enabled|default(false) -%}
|
||||
{%- set location_entry_selected = false -%}
|
||||
<tr id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}"{%- if css_class %} class="{{ css_class }}"{%- endif -%}>
|
||||
<td>
|
||||
<span id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_control_select_link" class="select_button">{{ control_select_link }}</span>
|
||||
</td>
|
||||
<td class="right" id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_actions">{{ control_edit_link }}{{ control_enabled_link }}</td>
|
||||
<td id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_name" onclick="$(this).selectText();">{{ location.name }}</td>
|
||||
<td id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_owner_name" onclick="$(this).selectText();">{{ player_dict.name }}</td>
|
||||
<td id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_id" onclick="$(this).selectText();">{{ location.identifier }}</td>
|
||||
<td class="right" id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_coordinates" onclick="$(this).selectText();">
|
||||
{{ ((location | default({})).coordinates | default({}) ).x }}
|
||||
{{ ((location | default({})).coordinates | default({}) ).y }}
|
||||
{{ ((location | default({})).coordinates | default({}) ).z }}
|
||||
</td>
|
||||
<td class="right" id="location_table_row_{{ location.dataset }}_{{ location.owner}}_{{ location.identifier }}_last_changed">{{ location.last_changed }}</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,34 @@
|
||||
{% set is_edit = location_to_edit_dict | length > 1 %}
|
||||
<header>
|
||||
<div>
|
||||
<span>Locations</span>
|
||||
</div>
|
||||
</header>
|
||||
<aside>
|
||||
{{ options_toggle }}
|
||||
</aside>
|
||||
<main>
|
||||
<table class="form_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Create a new location!</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{% with location_dict=location_to_edit_dict, is_edit=is_edit %}
|
||||
{% include "manage_locations_widget/view_create_new_set_name.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_identifier.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_select_shape.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_set_dimensions.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_set_coordinates.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_set_teleport_coordinates.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_select_type.html" %}
|
||||
{% include "manage_locations_widget/view_create_new_control_send_data.html" %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
@@ -0,0 +1,57 @@
|
||||
{% set action = "edit" if is_edit else "create_new" %}
|
||||
<!-- This logic is for the UX experience only, all values are checked for validity in the bots backend //-->
|
||||
<script>
|
||||
function send_location_data_to_bot() {
|
||||
let location_shape = $("input[name='location_shape']:checked").val();
|
||||
let checked_location_types = new Array();
|
||||
$.each($("input[name='location_type[]']:checked"), function() {
|
||||
checked_location_types.push($(this).val());
|
||||
});
|
||||
let location_coordinates_x = $("input[name='location_coordinates_x']").val();
|
||||
let location_coordinates_y = $("input[name='location_coordinates_y']").val();
|
||||
let location_coordinates_z = $("input[name='location_coordinates_z']").val();
|
||||
let location_coordinates = {
|
||||
'x': location_coordinates_x,
|
||||
'y': location_coordinates_y,
|
||||
'z': location_coordinates_z
|
||||
};
|
||||
|
||||
let location_teleport_entry_x = $("input[name='location_teleport_entry_x']").val();
|
||||
let location_teleport_entry_y = $("input[name='location_teleport_entry_y']").val();
|
||||
let location_teleport_entry_z = $("input[name='location_teleport_entry_z']").val();
|
||||
let location_teleport_entry = {
|
||||
'x': location_teleport_entry_x,
|
||||
'y': location_teleport_entry_y,
|
||||
'z': location_teleport_entry_z
|
||||
};
|
||||
|
||||
let location_dimensions_radius = $("input[name='location_dimensions_radius']").val();
|
||||
let location_dimensions_width = $("input[name='location_dimensions_width']").val();
|
||||
let location_dimensions_length = $("input[name='location_dimensions_length']").val();
|
||||
let location_dimensions_height = $("input[name='location_dimensions_height']").val();
|
||||
let location_dimensions = {
|
||||
'radius': location_dimensions_radius,
|
||||
'width': location_dimensions_width,
|
||||
'length': location_dimensions_length,
|
||||
'height': location_dimensions_height,
|
||||
};
|
||||
let location_name = $("input[name='location_name']").val();
|
||||
let location_identifier = $("input[name='location_identifier']").val();
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations', ['edit_location', {
|
||||
'location_identifier': location_identifier,
|
||||
'location_shape': location_shape,
|
||||
'location_type': checked_location_types,
|
||||
'location_coordinates': location_coordinates,
|
||||
'location_teleport_entry': location_teleport_entry,
|
||||
'location_dimensions': location_dimensions,
|
||||
'location_name': location_name,
|
||||
'location_owner': "{{ location_dict.owner }}",
|
||||
'is_enabled': "{{ location_dict.is_enabled }}",
|
||||
'action': "{{ action }}"
|
||||
}]]
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<a href="#" onclick="send_location_data_to_bot(); return false;">save</a>
|
||||
@@ -0,0 +1,35 @@
|
||||
<table class="box_input">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div>{%- if not is_edit %}
|
||||
<script>
|
||||
$("input[name='location_name']").on('input', function() {
|
||||
let location_name = $("input[name='location_name']").val();
|
||||
let location_identifier = location_name.replace(/[^a-zA-Z0-9]/g, '');
|
||||
$("input[name='location_identifier']").val(location_identifier);
|
||||
});
|
||||
</script>{% endif %}
|
||||
<label>
|
||||
<span>
|
||||
<input type="text" name="location_identifier" value="{{ location_dict.identifier }}" disabled="disabled" />
|
||||
location Identifier
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
This is the Identifier of your location<br />
|
||||
It is used to handle the location with in-game chat commands<br />
|
||||
It has to be unique for each users locations.<br />
|
||||
This setting can not be changed later on!<br />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,83 @@
|
||||
{% set checked_string = ' checked="checked"' %}
|
||||
{% set location_shape_not_found = true %}
|
||||
{% if location_dict["shape"] == "circle" %}
|
||||
{% set location_shape_circle_checked = checked_string %}
|
||||
{% set location_shape_not_found = false %}
|
||||
{% endif %}
|
||||
{% if location_dict["shape"] == "spherical" %}
|
||||
{% set location_shape_spherical_checked = checked_string %}
|
||||
{% set location_shape_not_found = false %}
|
||||
{% endif %}
|
||||
{% if location_dict["shape"] == "rectangular" %}
|
||||
{% set location_shape_rectangular_checked = checked_string %}
|
||||
{% set location_shape_not_found = false %}
|
||||
{% endif %}
|
||||
{% if location_dict["shape"] == "box" %}
|
||||
{% set location_shape_box_checked = checked_string %}
|
||||
{% set location_shape_not_found = false %}
|
||||
{% endif %}
|
||||
{% if location_shape_not_found %}
|
||||
{% set location_shape_rectangular_checked = checked_string %}
|
||||
{% endif %}
|
||||
<table class="box_select">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Choose the shape of your location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label><span>
|
||||
<input type="radio" name="location_shape" value="circle" {{ location_shape_circle_checked }}/>
|
||||
circle (2d)</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label>
|
||||
<span><input type="radio" name="location_shape" value="spherical" {{ location_shape_spherical_checked }}/>
|
||||
spherical (3d)</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label><span>
|
||||
<input type="radio" name="location_shape" value="rectangular" {{ location_shape_rectangular_checked }} />
|
||||
rectangular (2d)</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label><span>
|
||||
<input type="radio" name="location_shape" value="box" {{ location_shape_box_checked }} />
|
||||
box (3d)</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div>
|
||||
locations can have several different shapes.<br />
|
||||
you can have either a square (default, just like the LCB) or a round base, depending on your build-style<br />
|
||||
You can also limit the height of your locations, so you can build, for example,
|
||||
an underground base without affecting the ground above at all.<br />
|
||||
It is also useful for building multi-story builds with different locations per level,
|
||||
or even rooms on the same one.<br />
|
||||
This setting can be changed later on, at any time!
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,106 @@
|
||||
{% set checked_string = ' checked="checked"' %}
|
||||
{% set location_type_not_found = true %}
|
||||
{% if "is_village" in location_dict["type"] %}
|
||||
{% set location_type_village_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if "is_screamerfree" in location_dict["type"] %}
|
||||
{% set location_type_screamerfree_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if "is_home" in location_dict["type"] %}
|
||||
{% set location_type_home_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if "is_lobby" in location_dict["type"] %}
|
||||
{% set location_type_lobby_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if "is_onslaught" in location_dict["type"] %}
|
||||
{% set location_type_onslaught_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if "is_hunting_resort" in location_dict["type"] %}
|
||||
{% set location_type_hunting_resort_checked = checked_string %}
|
||||
{% set location_type_not_found = false %}
|
||||
{% endif %}
|
||||
{% if location_type_not_found %}{% endif %}
|
||||
<table class="box_select">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Choose the type of your location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Village
|
||||
<input type="checkbox" name="location_type[]" value="is_village" {{ location_type_village_checked }}/>
|
||||
<span>This location is a village and will have all village-functions enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Screamer-free
|
||||
<input type="checkbox" name="location_type[]" value="is_screamerfree" {{ location_type_screamerfree_checked }}/>
|
||||
<span>Screamers will be killed on arrival</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Onslaught
|
||||
<input type="checkbox" name="location_type[]" value="is_onslaught" {{ location_type_onslaught_checked }} />
|
||||
<span>Endless streams of zombies. Until you turn 'em off.</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Hunting Resort
|
||||
<input type="checkbox" name="location_type[]" value="is_hunting_resort" {{ location_type_hunting_resort_checked }} />
|
||||
<span>They are there, promise!!</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Home
|
||||
<input type="checkbox" name="location_type[]" value="is_home" {{ location_type_home_checked }} />
|
||||
<span>a player will be able to teleport to this under special circunstances</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="box">
|
||||
<label class="slider">Lobby
|
||||
<input type="checkbox" name="location_type[]" value="is_lobby" {{ location_type_lobby_checked }} />
|
||||
<span>No way out. Unless you know the password.<br />
|
||||
What's the word?<br />
|
||||
B-b-b-b-b-b-b-b-bird bird bird b-b-bird is the word :)</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div>
|
||||
locations can have a bunch of types<br />
|
||||
Types will add to one another, so you could create a home-lobby that is screamer-free and also it's own village :)<br />
|
||||
uncheck all to make it a simple location without any features attached. What for you ask?<br />
|
||||
Well, you could make your Builds available via locations, then people might find them easier.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,55 @@
|
||||
{% set coordinates = location_dict.coordinates|default({}) %}
|
||||
{% set pos_x = coordinates.x|default("0") %}
|
||||
{% set pos_y = coordinates.y|default("0") %}
|
||||
{% set pos_z = coordinates.z|default("0") %}
|
||||
<table class="box_input">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Choose the coordinates of your location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<label>x: <input size="6" name="location_coordinates_x" type="text" value="{{ pos_x }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<label>y: <input size="6" name="location_coordinates_y" type="text" value="{{ pos_y }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<label>z: <input size="6" name="location_coordinates_z" type="text" value="{{ pos_z }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<a href="#" onclick="use_current_coordinates2(); return false;">use current position</a>
|
||||
</div>
|
||||
</td>
|
||||
<script>
|
||||
function use_current_coordinates2() {
|
||||
$("input[name='location_coordinates_x']").val($("#location_coordinates_x").text());
|
||||
$("input[name='location_coordinates_y']").val($("#location_coordinates_y").text());
|
||||
$("input[name='location_coordinates_z']").val($("#location_coordinates_z").text());
|
||||
}
|
||||
</script>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<div>
|
||||
Sets the coordinates of your location<br />
|
||||
This will be the S/E corner of your location area. For round shapes, imagine a rectangle wrapped around
|
||||
it and use it's S/E corner
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,89 @@
|
||||
{% set dimensions = location_dict.dimensions|default({}) %}
|
||||
{% set location_dimensions_radius_value = dimensions.radius|default("0") %}
|
||||
{% set location_dimensions_width_value = dimensions.width|default("0") %}
|
||||
{% set location_dimensions_length_value = dimensions.length|default("0") %}
|
||||
{% set location_dimensions_height_value = dimensions.height|default("0") %}
|
||||
<table class="box_input">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Choose the size and dimensions of your location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<label><span>
|
||||
<input type="text" name="location_dimensions_radius" value="{{ location_dimensions_radius_value }}"/>
|
||||
Radius (current position is the center)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label><span>
|
||||
<input type="text" name="location_dimensions_width" value="{{ location_dimensions_width_value }}"/>
|
||||
Width (from the S/E corner going WEST)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label><span>
|
||||
<input type="text" name="location_dimensions_length" value="{{ location_dimensions_length_value }}"/>
|
||||
Length (from the S/E corner going NORTH)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label><span>
|
||||
<input type="text" name="location_dimensions_height" value="{{ location_dimensions_height_value }}"/>
|
||||
Height (from the S/E corner going UP)</span>
|
||||
</label>
|
||||
</div>
|
||||
<script>
|
||||
function set_shape_control_status() {
|
||||
let location_shape = $("input[name='location_shape']:checked").val();
|
||||
if (location_shape === "circle" || location_shape === "spherical") {
|
||||
$("input[name='location_dimensions_radius']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_width']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_length']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_height']").prop("disabled", true);
|
||||
}
|
||||
if (location_shape === "square" || location_shape === "cubical") {
|
||||
$("input[name='location_dimensions_radius']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_width']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_length']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_height']").prop("disabled", true);
|
||||
}
|
||||
if (location_shape === "rectangular") {
|
||||
$("input[name='location_dimensions_radius']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_width']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_length']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_height']").prop("disabled", true);
|
||||
}
|
||||
if (location_shape === "box") {
|
||||
$("input[name='location_dimensions_radius']").prop("disabled", true);
|
||||
$("input[name='location_dimensions_width']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_length']").prop("disabled", false);
|
||||
$("input[name='location_dimensions_height']").prop("disabled", false);
|
||||
}
|
||||
}
|
||||
set_shape_control_status();
|
||||
$("input[name='location_shape']").on('change', function() {
|
||||
set_shape_control_status()
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
Sets the Dimensions of your location<br />
|
||||
Depending on it's shape, it's either by radius or by defining up to three connecting side-lengths<br />
|
||||
The radius is set from the position the player is standing on, rectangular shapes start from
|
||||
the S/E corner going N/W and UP.<br />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,32 @@
|
||||
<table class="box_input">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Choose the name of your location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<label><span>
|
||||
<input type="text" name="location_name" value="{{ location_dict.name }}"/>
|
||||
location name</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
Sets the name of your location<br />
|
||||
this is only for visual purposes, it has no bearing on it's function<br />
|
||||
This setting can be changed later on, at any time!
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,54 @@
|
||||
{% set teleport_entry = location_dict.teleport_entry|default({}) %}
|
||||
{% set pos_x = teleport_entry.x|default("0") %}
|
||||
{% set pos_y = teleport_entry.y|default("0") %}
|
||||
{% set pos_z = teleport_entry.z|default("0") %}
|
||||
<table class="box_input">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Choose the teleport-entry for this location
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<label>x: <input size="6" name="location_teleport_entry_x" type="text" value="{{ pos_x }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<label>y: <input size="6" name="location_teleport_entry_y" type="text" value="{{ pos_y }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<label>z: <input size="6" name="location_teleport_entry_z" type="text" value="{{ pos_z }}" /></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<a href="#" onclick="use_current_coordinates(); return false;">use current position</a>
|
||||
</div>
|
||||
</td>
|
||||
<script>
|
||||
function use_current_coordinates() {
|
||||
$("input[name='location_teleport_entry_x']").val($("#location_coordinates_x").text());
|
||||
$("input[name='location_teleport_entry_y']").val($("#location_coordinates_y").text());
|
||||
$("input[name='location_teleport_entry_z']").val($("#location_coordinates_z").text());
|
||||
}
|
||||
</script>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<div>
|
||||
Sets the teleport-entry point for your location<br />
|
||||
Players using the bots travel commands will emerge in this spot and not in the default S/E corner
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -0,0 +1,29 @@
|
||||
<header>
|
||||
<div>
|
||||
<span>Locations</span>
|
||||
</div>
|
||||
</header>
|
||||
<aside>
|
||||
{{ options_toggle }}
|
||||
</aside>
|
||||
<main>
|
||||
<table class="data_table">
|
||||
<caption>
|
||||
<span>adapt</span>
|
||||
</caption>
|
||||
<thead>
|
||||
{{ table_header }}
|
||||
</thead>
|
||||
<tbody id="location_table">
|
||||
{{ table_rows }}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
{{ table_footer }}
|
||||
</tfoot>
|
||||
</table>
|
||||
<div class="dialog">
|
||||
<div id="manage_locations_widget_modal" class="modal-content">
|
||||
<p>this is the text inside the modal</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -0,0 +1,474 @@
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="/static/leaflet.css"/>
|
||||
|
||||
<style>
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
background: #1a1a1a;
|
||||
border: 2px solid var(--lcars-hopbush);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
.location-marker {
|
||||
background-color: var(--lcars-golden-tanoi);
|
||||
border: 2px solid var(--lcars-tanoi);
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.player-marker {
|
||||
background-color: var(--lcars-anakiwa);
|
||||
border: 2px solid var(--lcars-mariner);
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.map-controls {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Custom Legend Styling */
|
||||
.map-legend {
|
||||
background: rgba(26, 26, 26, 0.95);
|
||||
border: 2px solid var(--lcars-hopbush);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--lcars-golden-tanoi);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
|
||||
min-width: 200px;
|
||||
}
|
||||
.map-legend h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--lcars-hopbush);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid var(--lcars-hopbush);
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.map-legend .legend-item {
|
||||
margin: 4px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.map-legend .legend-label {
|
||||
color: var(--lcars-anakiwa);
|
||||
font-weight: bold;
|
||||
}
|
||||
.map-legend .legend-value {
|
||||
color: var(--lcars-golden-tanoi);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Coordinates Display */
|
||||
.coordinates-display {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background: rgba(26, 26, 26, 0.95);
|
||||
border: 2px solid var(--lcars-anakiwa);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
color: var(--lcars-anakiwa);
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.coordinates-display .coord-label {
|
||||
font-weight: bold;
|
||||
color: var(--lcars-hopbush);
|
||||
}
|
||||
|
||||
/* Create Location Button */
|
||||
.create-location-btn {
|
||||
background: var(--lcars-hopbush);
|
||||
border: 2px solid var(--lcars-hopbush);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.create-location-btn:hover {
|
||||
background: var(--lcars-golden-tanoi);
|
||||
border-color: var(--lcars-golden-tanoi);
|
||||
color: #000;
|
||||
}
|
||||
.create-location-btn.active {
|
||||
background: var(--lcars-tanoi);
|
||||
border-color: var(--lcars-tanoi);
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<span>Locations Map</span>
|
||||
</div>
|
||||
</header>
|
||||
<aside>
|
||||
{{ control_switch_view }}
|
||||
</aside>
|
||||
<main>
|
||||
<div id="map"></div>
|
||||
|
||||
<!-- Leaflet JS -->
|
||||
<script src="/static/leaflet.js"></script>
|
||||
|
||||
<script>
|
||||
// Wait for Leaflet to load
|
||||
(function initMap() {
|
||||
if (typeof L === 'undefined') {
|
||||
setTimeout(initMap, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if map container exists
|
||||
const mapContainer = document.getElementById('map');
|
||||
if (!mapContainer) {
|
||||
console.error('[MAP] Map container #map not found!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 7D2D Projection (from Alloc's mod)
|
||||
const SDTD_Projection = {
|
||||
project: function (latlng) {
|
||||
return new L.Point(
|
||||
(latlng.lat) / Math.pow(2, 4),
|
||||
(latlng.lng) / Math.pow(2, 4)
|
||||
);
|
||||
},
|
||||
unproject: function (point) {
|
||||
return new L.LatLng(
|
||||
point.x * Math.pow(2, 4),
|
||||
point.y * Math.pow(2, 4)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 7D2D CRS (from Alloc's mod)
|
||||
const SDTD_CRS = L.extend({}, L.CRS.Simple, {
|
||||
projection: SDTD_Projection,
|
||||
transformation: new L.Transformation(1, 0, -1, 0),
|
||||
scale: function (zoom) {
|
||||
return Math.pow(2, zoom);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize map with 7D2D CRS
|
||||
const map = L.map('map', {
|
||||
crs: SDTD_CRS,
|
||||
center: [0, 0],
|
||||
zoom: 3,
|
||||
minZoom: -1,
|
||||
maxZoom: 7,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
// Create tile layer (Y-axis flipping handled by backend)
|
||||
const tileLayer = L.tileLayer('/map_tiles/{z}/{x}/{y}.png', {
|
||||
tileSize: 128,
|
||||
minNativeZoom: 0,
|
||||
minZoom: -1,
|
||||
maxNativeZoom: 4,
|
||||
maxZoom: 7
|
||||
}).addTo(map);
|
||||
|
||||
// Storage for markers and shapes
|
||||
const locationShapes = {};
|
||||
const playerMarkers = {};
|
||||
|
||||
// Data variables for locations and players (initially empty, loaded via socket.io)
|
||||
const locations = {};
|
||||
const players = {};
|
||||
|
||||
// ========================================
|
||||
// Location Shape Creation Function
|
||||
// ========================================
|
||||
{{ webmap_templates.location_shapes|safe }}
|
||||
|
||||
// ========================================
|
||||
// Player Popup Creation
|
||||
// ========================================
|
||||
{{ webmap_templates.player_popup|safe }}
|
||||
|
||||
// ========================================
|
||||
// Real-time Update Handlers
|
||||
// ========================================
|
||||
|
||||
// Remove old map handler if it exists from previous view load
|
||||
if (window.mapDataHandler) {
|
||||
window.socket.off('data', window.mapDataHandler);
|
||||
}
|
||||
|
||||
// Create NEW handler function that captures current local scope
|
||||
// This must be recreated each time to access current locationShapes/playerMarkers
|
||||
window.mapDataHandler = function(data) {
|
||||
console.log('[MAP] Received socket.io data:', data.data_type, data);
|
||||
|
||||
// Map metadata (gameprefs, dataset info)
|
||||
if (data.data_type === 'map_metadata' && data.payload) {
|
||||
gameprefs = data.payload.gameprefs || {};
|
||||
activeDataset = data.payload.active_dataset || 'Unknown';
|
||||
updateLegend();
|
||||
console.log('[MAP] Updated map metadata:', gameprefs, activeDataset);
|
||||
}
|
||||
|
||||
// Player position updates
|
||||
{{ webmap_templates.player_update_handler|safe }}
|
||||
|
||||
// Location updates and removals
|
||||
{{ webmap_templates.location_update_handler|safe }}
|
||||
|
||||
// Update legend counts after any changes
|
||||
if (data.data_type === 'player_position_update' || data.data_type === 'location_update' || data.data_type === 'location_remove') {
|
||||
updateLegend();
|
||||
}
|
||||
};
|
||||
|
||||
// Register the new handler
|
||||
window.socket.on('data', window.mapDataHandler);
|
||||
|
||||
// Fit map to show all markers and shapes on load
|
||||
const allMapObjects = Object.values(locationShapes).concat(Object.values(playerMarkers));
|
||||
|
||||
if (allMapObjects.length > 0) {
|
||||
const group = L.featureGroup(allMapObjects);
|
||||
map.fitBounds(group.getBounds().pad(0.1));
|
||||
}
|
||||
|
||||
// Fix map size after a short delay (in case container was hidden initially)
|
||||
setTimeout(function() {
|
||||
map.invalidateSize();
|
||||
}, 250);
|
||||
|
||||
// ========================================
|
||||
// FEATURE 1: Map Legend with Metadata (loaded via Socket.IO)
|
||||
// ========================================
|
||||
|
||||
// Map metadata variables (loaded via socket.io)
|
||||
let gameprefs = {};
|
||||
let activeDataset = 'Loading...';
|
||||
|
||||
// Create custom legend control
|
||||
const legend = L.control({position: 'topleft'});
|
||||
let legendDiv = null;
|
||||
|
||||
legend.onAdd = function (mapInstance) {
|
||||
legendDiv = L.DomUtil.create('div', 'map-legend');
|
||||
updateLegend();
|
||||
return legendDiv;
|
||||
};
|
||||
|
||||
// Function to update legend content
|
||||
function updateLegend() {
|
||||
if (!legendDiv) return;
|
||||
|
||||
const gameName = gameprefs['GameName'] || activeDataset;
|
||||
const gameWorld = gameprefs['GameWorld'] || 'N/A';
|
||||
const worldGenSeed = gameprefs['WorldGenSeed'] || 'N/A';
|
||||
const worldGenSize = gameprefs['WorldGenSize'] || 'N/A';
|
||||
const locationCount = Object.keys(locations).length;
|
||||
const playerCount = Object.keys(players).length;
|
||||
|
||||
legendDiv.innerHTML = `
|
||||
<h4>🗺️ Map Info</h4>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">World:</span>
|
||||
<span class="legend-value">${gameName}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">Type:</span>
|
||||
<span class="legend-value">${gameWorld}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">Size:</span>
|
||||
<span class="legend-value">${worldGenSize}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">Seed:</span>
|
||||
<span class="legend-value">${worldGenSeed}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">Locations:</span>
|
||||
<span class="legend-value" id="legend-location-count">${locationCount}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-label">Players:</span>
|
||||
<span class="legend-value" id="legend-player-count">${playerCount}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
legend.addTo(map);
|
||||
|
||||
// ========================================
|
||||
// FEATURE 2: Coordinates Under Mouse Cursor
|
||||
// ========================================
|
||||
|
||||
// Create coordinates display element
|
||||
const coordsDiv = L.DomUtil.create('div', 'coordinates-display');
|
||||
coordsDiv.innerHTML = '<span class="coord-label">X:</span> <span id="coord-x">0</span> | ' +
|
||||
'<span class="coord-label">Z:</span> <span id="coord-z">0</span>';
|
||||
document.getElementById('map').appendChild(coordsDiv);
|
||||
|
||||
// Update coordinates on mouse move
|
||||
map.on('mousemove', function(e) {
|
||||
const coords = e.latlng;
|
||||
// In 7D2D, lat corresponds to X and lng to Z
|
||||
document.getElementById('coord-x').textContent = Math.round(coords.lat);
|
||||
document.getElementById('coord-z').textContent = Math.round(coords.lng);
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// FEATURE 3: Create Location UI
|
||||
// ========================================
|
||||
|
||||
// Store tempMarker globally so it can be accessed by confirm/cancel functions
|
||||
let tempMarker = null;
|
||||
|
||||
// Create custom control for location creation
|
||||
const createLocationControl = L.control({position: 'topright'});
|
||||
|
||||
createLocationControl.onAdd = function (mapInstance) {
|
||||
const btn = L.DomUtil.create('button', 'create-location-btn');
|
||||
btn.innerHTML = '📍 Create Location';
|
||||
btn.title = 'Click to enable location creation mode';
|
||||
|
||||
// Prevent map interactions when clicking the button
|
||||
L.DomEvent.disableClickPropagation(btn);
|
||||
|
||||
let creationMode = false;
|
||||
|
||||
btn.onclick = function() {
|
||||
creationMode = !creationMode;
|
||||
|
||||
if (creationMode) {
|
||||
btn.classList.add('active');
|
||||
btn.innerHTML = '✖️ Cancel';
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Add click handler for location creation
|
||||
map.once('click', function(e) {
|
||||
const coords = e.latlng;
|
||||
const x = Math.round(coords.lat);
|
||||
const z = Math.round(coords.lng);
|
||||
|
||||
// Create temporary marker
|
||||
tempMarker = L.circleMarker([coords.lat, coords.lng], {
|
||||
radius: 8,
|
||||
fillColor: '#ff00ff',
|
||||
color: '#ff00ff',
|
||||
weight: 3,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.6
|
||||
}).addTo(map);
|
||||
|
||||
tempMarker.bindPopup(
|
||||
'<div style="text-align: center;">' +
|
||||
'<b>New Location</b><br>' +
|
||||
'X: ' + x + ', Z: ' + z + '<br>' +
|
||||
'<button onclick="confirmLocation(' + x + ', ' + z + ')" ' +
|
||||
'style="margin: 5px; padding: 5px 10px; background: var(--lcars-hopbush); color: white; border: none; border-radius: 4px; cursor: pointer;">' +
|
||||
'Create Here</button>' +
|
||||
'<button onclick="cancelLocation()" ' +
|
||||
'style="margin: 5px; padding: 5px 10px; background: #666; color: white; border: none; border-radius: 4px; cursor: pointer;">' +
|
||||
'Cancel</button>' +
|
||||
'</div>'
|
||||
).openPopup();
|
||||
|
||||
// Reset button state
|
||||
btn.classList.remove('active');
|
||||
btn.innerHTML = '📍 Create Location';
|
||||
map.getContainer().style.cursor = '';
|
||||
creationMode = false;
|
||||
});
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.innerHTML = '📍 Create Location';
|
||||
map.getContainer().style.cursor = '';
|
||||
|
||||
// Remove temp marker if exists
|
||||
if (tempMarker) {
|
||||
map.removeLayer(tempMarker);
|
||||
tempMarker = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return btn;
|
||||
};
|
||||
|
||||
createLocationControl.addTo(map);
|
||||
|
||||
// Global functions for location creation confirmation
|
||||
window.confirmLocation = function(x, z) {
|
||||
// Navigate to create_new view with pre-filled coordinates using WebSocket
|
||||
// This follows the exact same pattern as all other action calls in the system
|
||||
window.socket.emit(
|
||||
'widget_event', // Socket event name
|
||||
['locations', // Module identifier
|
||||
['toggle_locations_widget_view', // Action identifier
|
||||
{ // Action parameters
|
||||
'action': 'show_create_new',
|
||||
'prefill_x': x,
|
||||
'prefill_z': z,
|
||||
'prefill_y': 0 // Default Y coordinate
|
||||
}]]
|
||||
);
|
||||
|
||||
console.log('[MAP] Navigating to create location form with coordinates:', x, z);
|
||||
|
||||
// Remove temp marker
|
||||
if (tempMarker) {
|
||||
map.removeLayer(tempMarker);
|
||||
tempMarker = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.cancelLocation = function() {
|
||||
// Remove temp marker
|
||||
if (tempMarker) {
|
||||
map.removeLayer(tempMarker);
|
||||
tempMarker = null;
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// Location Actions (Edit, Enable, Move, Set Teleport)
|
||||
// ========================================
|
||||
{{ webmap_templates.location_actions|safe }}
|
||||
|
||||
// ========================================
|
||||
// Player Actions (Kick, Message, Mute, Auth, Teleport)
|
||||
// ========================================
|
||||
{{ webmap_templates.player_actions|safe }}
|
||||
|
||||
})();
|
||||
</script>
|
||||
</main>
|
||||
@@ -0,0 +1,27 @@
|
||||
<header>
|
||||
<div>
|
||||
<span>Locations</span>
|
||||
</div>
|
||||
</header>
|
||||
<aside>
|
||||
{{ options_toggle }}
|
||||
</aside>
|
||||
<main>
|
||||
<table class="options_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">location widget options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="2"><span>location-options</span></th>
|
||||
</tr>
|
||||
{% for key, value in widget_options.items() %}
|
||||
<tr>
|
||||
<td><span>{{key}}</span></td><td>{{value}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
172
bot/modules/locations/templates/webmap/location_actions.html
Normal file
172
bot/modules/locations/templates/webmap/location_actions.html
Normal file
@@ -0,0 +1,172 @@
|
||||
// ========================================
|
||||
// Location Popup Actions
|
||||
// ========================================
|
||||
|
||||
// Edit location from map popup
|
||||
window.editLocationFromMap = function(dataset, owner, identifier) {
|
||||
// This follows the exact same pattern as control_edit_link.html
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['toggle_locations_widget_view', {
|
||||
'dom_element_owner': owner,
|
||||
'dom_element_identifier': identifier,
|
||||
'dom_element_origin': dataset,
|
||||
'action': 'edit_location_entry'
|
||||
}]]
|
||||
);
|
||||
console.log('[MAP] Opening edit view for location:', identifier);
|
||||
};
|
||||
|
||||
// Toggle enabled status from map popup
|
||||
window.toggleLocationEnabled = function(dataset, owner, identifier, isChecked) {
|
||||
// This follows the exact same pattern as control_enabled_link.html
|
||||
const action = isChecked ? 'enable_location_entry' : 'disable_location_entry';
|
||||
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['toggle_enabled_flag', {
|
||||
'dom_element_owner': owner,
|
||||
'dom_element_identifier': identifier,
|
||||
'dom_element_origin': dataset,
|
||||
'action': action
|
||||
}]]
|
||||
);
|
||||
console.log('[MAP] Toggling location enabled status:', identifier, 'to', isChecked);
|
||||
};
|
||||
|
||||
// Move location to new position (relative teleport)
|
||||
window.moveLocationFromMap = function(locationId) {
|
||||
const loc = locations[locationId];
|
||||
if (!loc) {
|
||||
console.error('[MAP] Location not found:', locationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open popups
|
||||
map.closePopup();
|
||||
|
||||
// Create info message
|
||||
const infoDiv = L.DomUtil.create('div', 'coordinates-display');
|
||||
infoDiv.style.bottom = '50px';
|
||||
infoDiv.style.background = 'rgba(102, 204, 255, 0.95)';
|
||||
infoDiv.style.borderColor = 'var(--lcars-anakiwa)';
|
||||
infoDiv.style.color = '#000';
|
||||
infoDiv.style.fontWeight = 'bold';
|
||||
infoDiv.innerHTML = '📍 Click new location position on map';
|
||||
document.getElementById('map').appendChild(infoDiv);
|
||||
|
||||
// Change cursor
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Wait for click
|
||||
map.once('click', function(e) {
|
||||
const newCoords = e.latlng;
|
||||
const newX = Math.round(newCoords.lat);
|
||||
const newZ = Math.round(newCoords.lng);
|
||||
|
||||
// Calculate offset
|
||||
const offsetX = newX - loc.coordinates.x;
|
||||
const offsetZ = newZ - loc.coordinates.z;
|
||||
|
||||
// Move teleport_entry relatively if it exists
|
||||
let newTeleportEntry = loc.teleport_entry || {};
|
||||
if (newTeleportEntry.x !== undefined && newTeleportEntry.y !== undefined && newTeleportEntry.z !== undefined) {
|
||||
newTeleportEntry = {
|
||||
x: (parseFloat(newTeleportEntry.x) || 0) + offsetX,
|
||||
y: parseFloat(newTeleportEntry.y) || 0, // Y stays same
|
||||
z: (parseFloat(newTeleportEntry.z) || 0) + offsetZ
|
||||
};
|
||||
}
|
||||
|
||||
// Call edit_location with ALL fields
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['edit_location', {
|
||||
'location_identifier': loc.identifier,
|
||||
'location_name': loc.name,
|
||||
'location_shape': loc.shape,
|
||||
'location_type': loc.type || [],
|
||||
'location_coordinates': {
|
||||
'x': newX,
|
||||
'y': loc.coordinates.y,
|
||||
'z': newZ
|
||||
},
|
||||
'location_teleport_entry': newTeleportEntry,
|
||||
'location_dimensions': loc.dimensions || {},
|
||||
'location_owner': loc.owner,
|
||||
'is_enabled': loc.is_enabled
|
||||
}]]
|
||||
);
|
||||
|
||||
console.log('[MAP] Moved location to:', newX, newZ, 'Offset:', offsetX, offsetZ);
|
||||
|
||||
// Cleanup
|
||||
map.getContainer().style.cursor = '';
|
||||
document.getElementById('map').removeChild(infoDiv);
|
||||
});
|
||||
};
|
||||
|
||||
// Set teleport coordinates
|
||||
window.setTeleportFromMap = function(locationId) {
|
||||
const loc = locations[locationId];
|
||||
if (!loc) {
|
||||
console.error('[MAP] Location not found:', locationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open popups
|
||||
map.closePopup();
|
||||
|
||||
// Create info message
|
||||
const infoDiv = L.DomUtil.create('div', 'coordinates-display');
|
||||
infoDiv.style.bottom = '50px';
|
||||
infoDiv.style.background = 'rgba(255, 153, 0, 0.95)';
|
||||
infoDiv.style.borderColor = 'var(--lcars-golden-tanoi)';
|
||||
infoDiv.style.color = '#000';
|
||||
infoDiv.style.fontWeight = 'bold';
|
||||
infoDiv.innerHTML = '🎯 Click teleport destination on map';
|
||||
document.getElementById('map').appendChild(infoDiv);
|
||||
|
||||
// Change cursor
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Wait for click
|
||||
map.once('click', function(e) {
|
||||
const teleportCoords = e.latlng;
|
||||
const tpX = Math.round(teleportCoords.lat);
|
||||
const tpZ = Math.round(teleportCoords.lng);
|
||||
|
||||
// Use current Y coordinate or default to location Y
|
||||
const tpY = (loc.teleport_entry && loc.teleport_entry.y) || loc.coordinates.y;
|
||||
|
||||
// Call edit_location with ALL fields
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['edit_location', {
|
||||
'location_identifier': loc.identifier,
|
||||
'location_name': loc.name,
|
||||
'location_shape': loc.shape,
|
||||
'location_type': loc.type || [],
|
||||
'location_coordinates': loc.coordinates,
|
||||
'location_teleport_entry': {
|
||||
'x': tpX,
|
||||
'y': tpY,
|
||||
'z': tpZ
|
||||
},
|
||||
'location_dimensions': loc.dimensions || {},
|
||||
'location_owner': loc.owner,
|
||||
'is_enabled': loc.is_enabled
|
||||
}]]
|
||||
);
|
||||
|
||||
console.log('[MAP] Set teleport to:', tpX, tpY, tpZ);
|
||||
|
||||
// Cleanup
|
||||
map.getContainer().style.cursor = '';
|
||||
document.getElementById('map').removeChild(infoDiv);
|
||||
});
|
||||
};
|
||||
143
bot/modules/locations/templates/webmap/location_shapes.html
Normal file
143
bot/modules/locations/templates/webmap/location_shapes.html
Normal file
@@ -0,0 +1,143 @@
|
||||
// Helper function to create location shape based on type
|
||||
function createLocationShape(locationId, loc) {
|
||||
const coords = loc.coordinates;
|
||||
const centerLatLng = [coords.x, coords.z]; // 7D2D coordinates (x, z)
|
||||
const dims = loc.dimensions || {};
|
||||
const shape = loc.shape || 'circle';
|
||||
const isEnabled = loc.is_enabled;
|
||||
const is3D = (shape === 'box' || shape === 'spherical');
|
||||
|
||||
// Color scheme
|
||||
const fillColor = isEnabled ? '#ff9900' : '#666666';
|
||||
const strokeColor = isEnabled ? '#ffcc00' : '#999999';
|
||||
const fillOpacity = isEnabled ? 0.3 : 0.15;
|
||||
|
||||
let leafletShape;
|
||||
|
||||
if (shape === 'circle') {
|
||||
const radius = parseFloat(dims.radius || 10);
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: radius,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
} else if (shape === 'spherical') {
|
||||
const radius = parseFloat(dims.radius || 10);
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: radius,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity,
|
||||
dashArray: '5, 5' // Dashed to indicate 3D
|
||||
});
|
||||
} else if (shape === 'rectangular') {
|
||||
const width = parseFloat(dims.width || 10);
|
||||
const length = parseFloat(dims.length || 10);
|
||||
// Rectangle bounds: from center, extend width/length in both directions
|
||||
const bounds = [
|
||||
[coords.x - width, coords.z - length],
|
||||
[coords.x + width, coords.z + length]
|
||||
];
|
||||
leafletShape = L.rectangle(bounds, {
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
} else if (shape === 'box') {
|
||||
const width = parseFloat(dims.width || 10);
|
||||
const length = parseFloat(dims.length || 10);
|
||||
const bounds = [
|
||||
[coords.x - width, coords.z - length],
|
||||
[coords.x + width, coords.z + length]
|
||||
];
|
||||
leafletShape = L.rectangle(bounds, {
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity,
|
||||
dashArray: '5, 5' // Dashed to indicate 3D
|
||||
});
|
||||
} else {
|
||||
// Fallback to circle
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: 10,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
}
|
||||
|
||||
// Build popup content
|
||||
const dimensionText = shape === 'circle' || shape === 'spherical'
|
||||
? `Radius: ${dims.radius || 'N/A'}`
|
||||
: `Width: ${dims.width || 'N/A'}, Length: ${dims.length || 'N/A'}${shape === 'box' ? ', Height: ' + (dims.height || 'N/A') : ''}`;
|
||||
|
||||
// Parse locationId to extract components
|
||||
// Format: {dataset}_{owner}_{identifier}
|
||||
const locationIdParts = locationId.split('_');
|
||||
const dataset = locationIdParts.slice(0, -2).join('_'); // Handle datasets with underscores
|
||||
const owner = locationIdParts[locationIdParts.length - 2];
|
||||
const identifier = locationIdParts[locationIdParts.length - 1];
|
||||
|
||||
const teleportEntry = loc.teleport_entry || {};
|
||||
const hasTeleport = teleportEntry.x !== undefined && teleportEntry.y !== undefined && teleportEntry.z !== undefined;
|
||||
const teleportText = hasTeleport
|
||||
? `TP: ${parseFloat(teleportEntry.x || 0).toFixed(0)}, ${parseFloat(teleportEntry.y || 0).toFixed(0)}, ${parseFloat(teleportEntry.z || 0).toFixed(0)}`
|
||||
: 'TP: Not set';
|
||||
|
||||
// Use template literal for clean HTML
|
||||
const popupContent = `
|
||||
<div style="min-width: 250px; font-family: monospace;">
|
||||
<b style="font-size: 1.1em;">${loc.name}</b>
|
||||
<br><span style="font-size: 0.9em; color: #888;">${is3D ? '🎲 3D' : '⬜ 2D'} - ${shape}</span>
|
||||
<br><hr style="margin: 5px 0; border-color: #333;">
|
||||
<b>Type:</b> ${loc.type && loc.type.length > 0 ? loc.type.join(', ') : 'None'}
|
||||
<br><b>Owner:</b> ${loc.owner}
|
||||
<br><b>Status:</b> ${isEnabled ? '✅ Enabled' : '❌ Disabled'}
|
||||
<br><b>Position:</b> ${coords.x.toFixed(0)}, ${coords.y.toFixed(0)}, ${coords.z.toFixed(0)}
|
||||
<br><b>Dimensions:</b> ${dimensionText}
|
||||
<br><b>${teleportText}</b>
|
||||
<br><hr style="margin: 8px 0; border-color: #333;">
|
||||
<div style="display: flex; gap: 5px; justify-content: space-between; align-items: center; margin-bottom: 5px;">
|
||||
<button onclick="editLocationFromMap('${dataset}', '${owner}', '${identifier}')"
|
||||
style="flex: 1; padding: 6px 12px; background: var(--lcars-hopbush); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||
✏️ Edit</button>
|
||||
<label style="display: flex; align-items: center; gap: 5px; cursor: pointer; white-space: nowrap;">
|
||||
<input type="checkbox"
|
||||
id="enable_${locationId}"
|
||||
${isEnabled ? 'checked' : ''}
|
||||
onchange="toggleLocationEnabled('${dataset}', '${owner}', '${identifier}', this.checked)"
|
||||
style="cursor: pointer; width: 18px; height: 18px;" />
|
||||
<span style="font-weight: bold;">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<button onclick="moveLocationFromMap('${locationId}')"
|
||||
style="flex: 1; padding: 6px 10px; background: var(--lcars-anakiwa); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
|
||||
📍 Move</button>
|
||||
<button onclick="setTeleportFromMap('${locationId}')"
|
||||
style="flex: 1; padding: 6px 10px; background: var(--lcars-golden-tanoi); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
|
||||
🎯 Set TP</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
leafletShape.bindPopup(popupContent);
|
||||
leafletShape.addTo(map);
|
||||
|
||||
return leafletShape;
|
||||
}
|
||||
|
||||
// Location shapes are now loaded dynamically via Socket.IO
|
||||
// Initial loading is handled by location_update events
|
||||
// See location_update_handler.html for shape creation logic
|
||||
@@ -0,0 +1,32 @@
|
||||
// Listen for location updates/additions
|
||||
if (data.data_type === 'location_update' && data.payload && data.payload.location) {
|
||||
const locationId = data.payload.location_id;
|
||||
const loc = data.payload.location;
|
||||
|
||||
// Update locations dictionary with new data
|
||||
locations[locationId] = loc;
|
||||
|
||||
// Remove old shape if exists
|
||||
if (locationShapes[locationId]) {
|
||||
map.removeLayer(locationShapes[locationId]);
|
||||
delete locationShapes[locationId];
|
||||
}
|
||||
|
||||
// Create new shape
|
||||
try {
|
||||
const shape = createLocationShape(locationId, loc);
|
||||
locationShapes[locationId] = shape;
|
||||
} catch (error) {
|
||||
console.error('[MAP] Error updating location shape:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for location removals
|
||||
if (data.data_type === 'location_remove' && data.payload && data.payload.location_id) {
|
||||
const locationId = data.payload.location_id;
|
||||
|
||||
if (locationShapes[locationId]) {
|
||||
map.removeLayer(locationShapes[locationId]);
|
||||
delete locationShapes[locationId];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user