Release 0.9.0

This commit is contained in:
2025-11-21 07:26:02 +01:00
committed by ecv
commit 472f0812e7
240 changed files with 20033 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
from bot.module import Module
from bot import loaded_modules_dict
from time import time
import math
class Locations(Module):
dom_element_root = list
dom_element_select_root = list
default_max_locations = int
standard_location_shape = str
def __init__(self):
setattr(self, "default_options", {
"module_name": self.get_module_identifier()[7:],
"dom_element_root": ["%dom_element_identifier%"],
"dom_element_select_root": ["%dom_element_identifier%", "selected_by"],
"default_max_locations": 3,
"standard_location_shape": "rectangular",
"run_observer_interval": 3,
"run_observer_interval_idle": 10
})
setattr(self, "required_modules", [
'module_dom',
'module_dom_management',
'module_game_environment',
'module_players',
'module_telnet',
'module_webserver'
])
self.next_cycle = 0
self.all_available_actions_dict = {}
self.all_available_widgets_dict = {}
Module.__init__(self)
@staticmethod
def get_module_identifier():
return "module_locations"
# region Standard module stuff
def setup(self, options=dict):
Module.setup(self, options)
self.run_observer_interval = self.options.get(
"run_observer_interval", self.default_options.get("run_observer_interval", None)
)
self.run_observer_interval_idle = self.options.get(
"run_observer_interval_idle", self.default_options.get("run_observer_interval_idle", None)
)
self.dom_element_root = self.options.get(
"dom_element_root", self.default_options.get("dom_element_root", None)
)
self.dom_element_select_root = self.options.get(
"dom_element_select_root", self.default_options.get("dom_element_select_root", None)
)
self.default_max_locations = self.options.get(
"default_max_locations", self.default_options.get("default_max_locations", None)
)
self.standard_location_shape = self.options.get(
"standard_location_shape", self.default_options.get("standard_location_shape", None)
)
# endregion
def start(self):
""" all modules have been loaded and initialized by now. we can bend the rules here."""
Module.start(self)
# endregion
def get_elements_by_type(self, location_type: str, var=None):
if var is None:
active_dataset = self.dom.data.get("module_game_environment", {}).get("active_dataset", None)
var = (
self.dom.data
.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
)
if hasattr(var, 'items'):
for k, v in var.items():
if k == "type":
if location_type in v:
yield var
if isinstance(v, dict):
for result in self.get_elements_by_type(location_type, v):
yield result
elif isinstance(v, list):
for d in v:
for result in self.get_elements_by_type(location_type, d):
yield result
@staticmethod
def get_location_volume(location_dict):
"""
Calculate the 3D volume coordinates for a box-shaped location.
Handles different map quadrants (SW, SE, NE, NW) with appropriate
coordinate adjustments for the game's coordinate system.
Args:
location_dict: Dictionary containing shape, dimensions, and coordinates
Returns:
Dictionary with pos_x, pos_y, pos_z, pos_x2, pos_y2, pos_z2
or None if not a box shape
"""
shape = location_dict.get("shape", None)
if shape != "box":
return None
dimensions = location_dict.get("dimensions", None)
coords = location_dict.get("coordinates", None)
# Convert coordinates to integers
x = int(float(coords["x"]))
y = int(float(coords["y"]))
z = int(float(coords["z"]))
width = float(dimensions["width"])
height = float(dimensions["height"])
length = float(dimensions["length"])
# Determine quadrant and calculate adjustments
is_west = x < 0 # West quadrants (SW, NW)
is_south = z < 0 # South quadrants (SW, SE)
# Base coordinates
pos_x = x - 1 if is_west else x
pos_z = z - 1
# Z2 adjustment depends on quadrant
z2_offset = -2 if is_south else -1
return {
"pos_x": pos_x,
"pos_y": y,
"pos_z": pos_z,
"pos_x2": int(x - width),
"pos_y2": int(y + height - 1),
"pos_z2": int(z + length + z2_offset)
}
@staticmethod
def _is_inside_sphere(player_pos, center, radius):
"""Check if position is inside a 3D sphere."""
distance = math.sqrt(
(player_pos['x'] - center['x']) ** 2 +
(player_pos['y'] - center['y']) ** 2 +
(player_pos['z'] - center['z']) ** 2
)
return distance <= radius
@staticmethod
def _is_inside_circle(player_pos, center, radius):
"""Check if position is inside a 2D circle (ignores Y axis)."""
distance = math.sqrt(
(player_pos['x'] - center['x']) ** 2 +
(player_pos['z'] - center['z']) ** 2
)
return distance <= radius
@staticmethod
def _is_inside_box(player_pos, corner, dimensions):
"""Check if position is inside a 3D box."""
return all([
player_pos['x'] - dimensions['width'] <= corner['x'] <= player_pos['x'] + dimensions['width'],
player_pos['y'] - dimensions['height'] <= corner['y'] <= player_pos['y'] + dimensions['height'],
player_pos['z'] - dimensions['length'] <= corner['z'] <= player_pos['z'] + dimensions['length']
])
@staticmethod
def _is_inside_rectangle(player_pos, corner, dimensions):
"""Check if position is inside a 2D rectangle (ignores Y axis)."""
return all([
player_pos['x'] - dimensions['width'] <= corner['x'] <= player_pos['x'] + dimensions['width'],
player_pos['z'] - dimensions['length'] <= corner['z'] <= player_pos['z'] + dimensions['length']
])
@staticmethod
def position_is_inside_boundary(position_dict=None, boundary_dict=None):
"""
Check if a position is inside a boundary shape.
Supports multiple shape types: spherical, circle, box, rectangular.
Args:
position_dict: Dictionary with 'pos' containing x, y, z coordinates
boundary_dict: Dictionary with 'shape', 'dimensions', 'coordinates'
Returns:
bool: True if position is inside boundary, False otherwise
"""
if not all([position_dict, boundary_dict]):
return False
shape = boundary_dict.get("shape")
dimensions = boundary_dict.get("dimensions")
coordinates = boundary_dict.get("coordinates")
if not all([shape, dimensions, coordinates]):
return False
# Extract player position
player_pos = {
'x': float(position_dict.get("pos", {}).get("x", 0)),
'y': float(position_dict.get("pos", {}).get("y", 0)),
'z': float(position_dict.get("pos", {}).get("z", 0))
}
# Extract boundary center/corner coordinates
boundary_coords = {
'x': float(coordinates.get("x", 0)),
'y': float(coordinates.get("y", 0)),
'z': float(coordinates.get("z", 0))
}
# Check shape type and delegate to appropriate helper
if shape == "spherical":
radius = float(dimensions.get("radius", 0))
return Locations._is_inside_sphere(player_pos, boundary_coords, radius)
elif shape == "circle":
radius = float(dimensions.get("radius", 0))
return Locations._is_inside_circle(player_pos, boundary_coords, radius)
elif shape == "box":
dims = {
'width': float(dimensions.get("width", 0)),
'height': float(dimensions.get("height", 0)),
'length': float(dimensions.get("length", 0))
}
return Locations._is_inside_box(player_pos, boundary_coords, dims)
elif shape == "rectangular":
dims = {
'width': float(dimensions.get("width", 0)),
'length': float(dimensions.get("length", 0))
}
return Locations._is_inside_rectangle(player_pos, boundary_coords, dims)
return False
def run(self):
while not self.stopped.wait(self.next_cycle):
profile_start = time()
self.last_execution_time = time() - profile_start
self.next_cycle = self.run_observer_interval - self.last_execution_time
loaded_modules_dict[Locations().get_module_identifier()] = Locations()

View File

@@ -0,0 +1,55 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
event_data[1]["action_identifier"] = action_name
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
location_identifier = event_data[1].get("location_identifier")
location_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(dispatchers_steamid, {})
.get(location_identifier, None)
)
coordinates = module.get_location_volume(location_dict)
if coordinates is not None:
command = (
"bc-export {location_to_be_exported} {pos_x} {pos_y} {pos_z} {pos_x2} {pos_y2} {pos_z2}"
).format(
location_to_be_exported=location_dict.get("identifier"),
**coordinates
)
module.telnet.add_telnet_command_to_queue(command)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
return
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "Will export everything inside the locations Volume. Will only work for 'box' locations",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,97 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def fix_coordinates_for_bc_import(location_dict: dict, coordinates: dict, player_dict=None) -> bool:
shape = location_dict.get("shape", None)
dimensions = location_dict.get("dimensions", None)
location_coordinates = location_dict.get("coordinates", None)
if shape == "box":
if player_dict is None:
if int(float(location_coordinates["x"])) < 0: # W Half
coordinates["pos_x"] = int(float(coordinates["pos_x"]) - float(dimensions["width"]) + 1)
if int(float(location_coordinates["x"])) >= 0: # E Half
coordinates["pos_x"] = int(float(coordinates["pos_x"]) - float(dimensions["width"]))
else:
if int(float(player_dict.get("pos", {}).get("x"))) < 0: # W Half
coordinates["pos_x"] = int(float(player_dict.get("pos", {}).get("x")) - float(dimensions["width"]) - 1)
if int(float(player_dict.get("pos", {}).get("x"))) >= 0: # E Half
coordinates["pos_x"] = int(float(player_dict.get("pos", {}).get("x")) - float(dimensions["width"]))
coordinates["pos_y"] = int(float(player_dict.get("pos", {}).get("y")))
if int(float(player_dict.get("pos", {}).get("z"))) < 0: # S Half
coordinates["pos_z"] = int(float(player_dict.get("pos", {}).get("z")) - 1)
if int(float(player_dict.get("pos", {}).get("z"))) >= 0: # N Half
coordinates["pos_z"] = int(float(player_dict.get("pos", {}).get("z")))
return True
else:
return False
def main_function(module, event_data, dispatchers_steamid):
event_data[1]["action_identifier"] = action_name
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
location_identifier = event_data[1].get("location_identifier")
spawn_in_place = event_data[1].get("spawn_in_place")
location_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(dispatchers_steamid, {})
.get(location_identifier, None)
)
coordinates = module.get_location_volume(location_dict)
if coordinates is not None:
if spawn_in_place:
player_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(dispatchers_steamid, {})
)
fix_coordinates_for_bc_import(location_dict, coordinates, player_dict)
else:
fix_coordinates_for_bc_import(location_dict, coordinates)
command = (
"bc-import {location_to_be_imported} {pos_x} {pos_y} {pos_z}"
).format(
location_to_be_imported=location_dict.get("identifier"),
**coordinates
)
module.telnet.add_telnet_command_to_queue(command)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
return
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "Imports a saved prefab. Needs to have a location first!",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,82 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
location_identifier = event_data[1].get("location_identifier", None)
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
event_data[1]["action_identifier"] = action_name
event_data[1]["fail_reason"] = []
location_owner = event_data[1].get("location_owner", dispatchers_steamid)
location_name = event_data[1].get("location_name", None)
if location_identifier is None or location_identifier == "":
location_identifier = ''.join(e for e in location_name if e.isalnum())
location_shape = event_data[1].get("location_shape", module.default_options.get("standard_location_shape", None))
location_types = event_data[1].get("location_type", [])
location_coordinates = event_data[1].get("location_coordinates", {})
location_teleport_entry = event_data[1].get("location_teleport_entry", {})
location_dimensions = event_data[1].get("location_dimensions", {})
location_enabled = event_data[1].get("is_enabled", False)
last_changed = event_data[1].get("last_changed", False)
if all([
location_name is not None and len(location_name) >= 3,
location_identifier is not None,
location_shape is not None,
active_dataset is not None,
location_owner is not None
]):
module.dom.data.upsert({
module.get_module_identifier(): {
"elements": {
active_dataset: {
str(location_owner): {
location_identifier: {
"name": location_name,
"identifier": location_identifier,
"dataset": active_dataset,
"shape": location_shape,
"type": location_types,
"coordinates": location_coordinates,
"teleport_entry": location_teleport_entry,
"dimensions": location_dimensions,
"owner": str(location_owner),
"is_enabled": location_enabled,
"selected_by": [],
"last_changed": last_changed
}
}
}
}
}
}, dispatchers_steamid=dispatchers_steamid, max_callback_level=4)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
return
event_data[1]["fail_reason"].append("not all conditions met!")
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "manages location entries",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,48 @@
from bot import loaded_modules_dict
from os import path, pardir
from time import sleep, time
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
action = event_data[1].get("action", None)
event_data[1]["action_identifier"] = action_name
location_identifier = event_data[1].get("location_identifier", None)
if action == "start onslaught":
event_data = ['say_to_player', {
'steamid': dispatchers_steamid,
'message': '[66FF66]Onslaught[-][FFFFFF] Started for location [66FF66]{}[-]'.format(location_identifier)
}]
module.trigger_action_hook(module.players, event_data=event_data)
elif action == "stop onslaught":
event_data = ['say_to_player', {
'steamid': dispatchers_steamid,
'message': '[66FF66]Onslaught[-][FFFFFF] in location [66FF66]{}[-] Ended[-]'.format(location_identifier)
}]
module.trigger_action_hook(module.players, event_data=event_data)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "manages the onslaught event on dedicated locations",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": True,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,42 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
event_data[1]["action_identifier"] = action_name
location_coordinates = event_data[1].get("location_coordinates", {})
player_steamid = event_data[1].get("steamid", dispatchers_steamid)
if location_coordinates:
module.trigger_action_hook(
module.players, event_data=["teleport_player", {
"steamid": player_steamid,
"coordinates": location_coordinates
}])
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
return
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "Teleports a player to a set of coordinates",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,59 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
event_data[1]["action_identifier"] = action_name
action = event_data[1].get("action", None)
location_origin = event_data[1].get("dom_element_origin", None)
location_owner = event_data[1].get("dom_element_owner", None)
location_identifier = event_data[1].get("dom_element_identifier", None)
if all([
action is not None
]):
if action == "enable_location_entry" or action == "disable_location_entry":
element_is_enabled = action == "enable_location_entry"
module.dom.data.upsert({
"module_locations": {
"elements": {
location_origin: {
location_owner: {
location_identifier: {
"is_enabled": element_is_enabled
}
}
}
}
}
}, dispatchers_steamid=dispatchers_steamid, min_callback_level=4)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
return
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "Sets or removes the enabled flag of a location",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,72 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
action_name = path.basename(path.abspath(__file__))[:-3]
def main_function(module, event_data, dispatchers_steamid):
event_data[1]["action_identifier"] = action_name
action = event_data[1].get("action", None)
location_owner = event_data[1].get("dom_element_owner", None)
location_identifier = event_data[1].get("dom_element_identifier", None)
location_origin = event_data[1].get("dom_element_origin", None)
# Support for prefilled coordinates from map
prefill_x = event_data[1].get("prefill_x", None)
prefill_y = event_data[1].get("prefill_y", None)
prefill_z = event_data[1].get("prefill_z", None)
if action == "show_options":
current_view = "options"
elif action == "show_frontend":
current_view = "frontend"
elif action == "show_create_new":
current_view = "create_new"
elif action == "edit_location_entry":
current_view = "edit_location_entry"
elif action == "show_special_locations":
current_view = "special_locations"
elif action == "show_map":
current_view = "map"
else:
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
view_data = {
"current_view": current_view,
"location_owner": location_owner,
"location_identifier": location_identifier,
"location_origin": location_origin
}
# Add prefill data if creating new location
if current_view == "create_new" and any([prefill_x, prefill_y, prefill_z]):
view_data["prefill_coordinates"] = {
"x": prefill_x,
"y": prefill_y,
"z": prefill_z
}
module.set_current_view(dispatchers_steamid, view_data)
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
def callback_success(module, event_data, dispatchers_steamid, match=None):
pass
def callback_fail(module, event_data, dispatchers_steamid):
pass
action_meta = {
"description": "manages location stuff",
"main_function": main_function,
"callback_success": callback_success,
"callback_fail": callback_fail,
"requires_telnet_connection": False,
"enabled": True
}
loaded_modules_dict["module_" + module_name].register_action(action_name, action_meta)

View File

@@ -0,0 +1,64 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
steamid = regex_result.group("player_steamid")
result = re.match(r"^.*add\slocation\s(?P<location_name>.*)", command)
if result:
location_name = result.group("location_name")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
player_dict = module.dom.data.get("module_players", {}).get("elements", {}).get(active_dataset, {}).get(steamid, {})
if len(player_dict) >= 1 and result:
event_data = ['edit_location', {
'location_coordinates': {
"x": player_dict["pos"]["x"],
"y": player_dict["pos"]["y"],
"z": player_dict["pos"]["z"]
},
'location_name': location_name,
'action': 'create_new',
'last_changed': module.game_environment.get_last_recorded_gametime_string()
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=steamid)
triggers = {
"add location": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/add location.*)"
}
trigger_meta = {
"description": "catches location commands from the players chat and then adds them to the database",
"main_function": main_function,
"triggers": [
{
"identifier": "add location (Alloc)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["add location"]
),
"callback": main_function
},
{
"identifier": "add location (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["add location"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,77 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
player_steamid = regex_result.group("player_steamid")
location_dict = None
result = re.match(r"^.*export\slocation\s(?P<location_identifier>.*)(?:\s)?(?P<spawn_in_place>.*)?", command)
if result:
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
location_identifier = result.group("location_identifier")
location_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
.get(location_identifier, None)
)
location_name = location_dict.get("name")
else:
location_name = "None Provided"
location_identifier = "None"
if location_dict is not None:
event_data = ['bc-export', {
"location_identifier": location_identifier
}]
module.trigger_action_hook(origin_module.locations, event_data=event_data, dispatchers_steamid=player_steamid)
else:
event_data = ['say_to_player', {
'steamid': player_steamid,
'message': '[FFFFFF]Could not find [66FF66]{location_name} ({location_identifier})[-]'.format(
location_name=location_name,
location_identifier=location_identifier
)
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
triggers = {
"export location": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/export location.*)"
}
trigger_meta = {
"description": "will issue the BCM mods bc-export command on the specified location",
"main_function": main_function,
"triggers": [
{
"identifier": "export location",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["export location"]
),
"callback": main_function
},
{
"identifier": "export location",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["export location"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,78 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
player_steamid = regex_result.group("player_steamid")
location_dict = None
spawn_in_place = False
result = re.match(r"^.*import\slocation\s(?P<location_identifier>\S+)(?:\s)?(?P<spawn_in_place>here)?", command)
if result:
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
location_identifier = result.group("location_identifier")
location_dict = (
module.dom.data
.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
.get(location_identifier, None)
)
spawn_in_place = result.group("spawn_in_place") == "here"
if location_dict is not None:
event_data = ['bc-import', {
"location_identifier": location_identifier,
"spawn_in_place": spawn_in_place
}]
module.trigger_action_hook(origin_module.locations, event_data=event_data, dispatchers_steamid=player_steamid)
else:
event_data = ['say_to_player', {
'steamid': player_steamid,
'message': '[FFFFFF]Could not find [66FF66]{location_name} ({location_identifier})[-]'.format(
location_name="None Provided",
location_identifier="None"
)
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
triggers = {
"import location": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/import location.*)"
}
trigger_meta = {
"description": "will issue the BCM mods bc-import command on the specified location",
"main_function": main_function,
"triggers": [
{
"identifier": "import location (Allocs)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["import location"]
),
"callback": main_function
},
{
"identifier": "import location (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["import location"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,59 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
player_steamid = regex_result.group("player_steamid")
found_home = False
location_dict = {}
for home in origin_module.get_elements_by_type("is_home"):
if home.get("owner") == player_steamid:
location_dict = home
found_home = True
if found_home is True and len(location_dict) >= 1:
event_data = ['teleport_to_coordinates', {
'location_coordinates': {
"x": location_dict.get("teleport_entry", {}).get("x", location_dict["coordinates"]["x"]),
"y": location_dict.get("teleport_entry", {}).get("y", location_dict["coordinates"]["y"]),
"z": location_dict.get("teleport_entry", {}).get("z", location_dict["coordinates"]["z"])
}
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=player_steamid)
triggers = {
"send me home": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/send\sme\shome)"
}
trigger_meta = {
"description": "sends the player to his home, if available",
"main_function": main_function,
"triggers": [
{
"identifier": "send me home",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["send me home"]
),
"callback": main_function
},
{
"identifier": "send me home",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["send me home"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,102 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
player_steamid = regex_result.group("player_steamid")
result = re.match(r"^.*send\sme\sto\slocation\s(?P<location_identifier>.*)", command)
if result:
location_identifier = result.group("location_identifier")
else:
return
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
location_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
.get(location_identifier, {})
)
player_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
)
if len(player_dict) >= 1 and len(location_dict) >= 1:
teleport_entry = location_dict.get("teleport_entry", {})
teleport_entry_x = teleport_entry.get("x", None)
teleport_entry_y = teleport_entry.get("y", None)
teleport_entry_z = teleport_entry.get("z", None)
if any([
teleport_entry_x is None,
teleport_entry_y is None,
teleport_entry_z is None,
all([
int(teleport_entry_x) == 0,
int(teleport_entry_y) == 0,
int(teleport_entry_z) == 0,
])
]):
location_coordinates = {
"x": location_dict["coordinates"]["x"],
"y": location_dict["coordinates"]["y"],
"z": location_dict["coordinates"]["z"]
}
else:
location_coordinates = {
"x": teleport_entry_x,
"y": teleport_entry_y,
"z": teleport_entry_z
}
event_data = ['teleport_to_coordinates', {
'location_coordinates': location_coordinates
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=player_steamid)
triggers = {
"send me to location": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/send\sme\sto\slocation.*)"
}
trigger_meta = {
"description": (
"sends player to the location of their choosing, will use the teleport_entry coordinates if available"
),
"main_function": main_function,
"triggers": [
{
"identifier": "add location (Alloc)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["send me to location"]
),
"callback": main_function
},
{
"identifier": "add location (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["send me to location"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,116 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
import re
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
steamid = regex_result.group("player_steamid")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
player_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(steamid, {})
)
if len(player_dict) < 1:
return False
result = re.match(r"^.*st.*\sonslaught\s(?P<onslaught_options>.*)", command)
if result:
onslaught_options = result.group("onslaught_options")
else:
""" no options provided
might later chose the location one is standing in and owns, or let some other stuff happen
"""
onslaught_options = None
if command.startswith("/start onslaught"):
""" check if the player is inside a location which allows onslaught to be enabled """
# let's iterate through all suitable locations
for onslaught_location in origin_module.get_elements_by_type("is_onslaught"):
# only proceed with the player is inside a dedicated location
if any([
onslaught_options in ["everywhere", onslaught_location["identifier"]],
origin_module.position_is_inside_boundary(player_dict, onslaught_location)
]):
# fire onslaught in all selected locations
event_data = ['onslaught', {
'onslaught_options': onslaught_options,
'location_owner': onslaught_location['owner'],
'location_identifier': onslaught_location['identifier'],
'action': 'start onslaught'
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=steamid)
elif command.startswith("/stop onslaught"):
for onslaught_location in origin_module.get_elements_by_type("is_onslaught"):
# only proceed with the player is inside a dedicated location
if any([
onslaught_options in ["everywhere", onslaught_location["identifier"]],
origin_module.position_is_inside_boundary(player_dict, onslaught_location)
]):
# fire onslaught in all selected locations
event_data = ['onslaught', {
'location_owner': onslaught_location['owner'],
'location_identifier': onslaught_location['identifier'],
'action': 'stop onslaught'
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=steamid)
triggers = {
"start onslaught": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/start\sonslaught.*)",
"stop onslaught": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/stop\sonslaught.*)"
}
trigger_meta = {
"description": "will start the onslaught event in a specified location",
"main_function": main_function,
"triggers": [
{
"identifier": "start onslaught (Alloc)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["start onslaught"]
),
"callback": main_function
},
{
"identifier": "stop onslaught (Alloc)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["stop onslaught"]
),
"callback": main_function
},
{
"identifier": "start onslaught (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["start onslaught"]
),
"callback": main_function
},
{
"identifier": "stop onslaught (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["stop onslaught"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,64 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
location_identifier = "PlaceofDeath"
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
steamid = regex_result.group("player_steamid")
player_dict = module.dom.data.get("module_players", {}).get("elements", {}).get(active_dataset, {}).get(steamid, {})
location_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(steamid, {})
.get(location_identifier, {})
)
if len(player_dict) >= 1 and len(location_dict) >= 1:
event_data = ['teleport_to_coordinates', {
'location_coordinates': {
"x": location_dict["coordinates"]["x"],
"y": location_dict["coordinates"]["y"],
"z": location_dict["coordinates"]["z"]
}
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=steamid)
triggers = {
"take me to my grave": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/take me to my grave)"
}
trigger_meta = {
"description": "sends the player to his final resting place, if available",
"main_function": main_function,
"triggers": [
{
"identifier": "take me to my grave (Allocs)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["take me to my grave"]
),
"callback": main_function
},
{
"identifier": "take me to my grave (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["take me to my grave"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,100 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def find_by_key(data, target):
for key, value in data.items():
if isinstance(value, dict):
yield from find_by_key(value, target)
elif key == target:
yield value
def main_function(origin_module, module, regex_result):
player_steamid = regex_result.group("player_steamid")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
player_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
)
all_locations_dict = (
module.dom.data.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
)
occupied_locations = []
for locations_by_owner in all_locations_dict:
for location_identifier, location_dict in all_locations_dict[locations_by_owner].items():
if origin_module.position_is_inside_boundary(player_dict, location_dict):
occupied_locations.append(location_dict)
if len(occupied_locations) > 0:
event_data = ['say_to_player', {
'steamid': player_steamid,
'message': '[FFFFFF]You are inside the following locations:[-]'
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
for location_dict in occupied_locations:
location_owner_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(location_dict.get("owner"), {})
)
event_data = ['say_to_player', {
'steamid': player_steamid,
'message': '[FFFFFF]{name} [66FF66]({owner})[-]'.format(
name=location_dict.get("name", "n/a"),
owner=location_owner_dict.get("name", "n/a")
)
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
else:
event_data = ['say_to_player', {
'steamid': player_steamid,
'message': '[FFFFFF]You do not seem to be in any designated location[-]'
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
triggers = {
"where am i": r"\'(?P<player_name>.*)\'\:\s(?P<command>\/where am i)"
}
trigger_meta = {
"description": "prints out a list of locations a player currently occupies",
"main_function": main_function,
"triggers": [
{
"identifier": "where am i (Allocs)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["Allocs"]["chat"] +
triggers["where am i"]
),
"callback": main_function
},
{
"identifier": "where am i (BCM)",
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["BCM"]["chat"] +
triggers["where am i"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View 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 -%}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
<tr>
<td colspan="8">
<div>{{ action_delete_button }}</div>
</td>
</tr>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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);
});
};

View 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

View File

@@ -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];
}
}

View File

@@ -0,0 +1,113 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def send_location_update_to_map(*args, **kwargs):
"""Send location updates to map view via socket.io"""
module = args[0]
method = kwargs.get("method", None)
updated_values_dict = kwargs.get("updated_values_dict", None)
if updated_values_dict is None:
return
# Check which clients are viewing the map
for clientid in module.webserver.connected_clients.keys():
current_view = module.get_current_view(clientid)
if current_view != "map":
continue
if method in ["upsert", "update", "edit"]:
# Send location update for each changed location
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
# updated_values_dict structure at callback depth 4:
# {location_identifier: {location_data}}
# location_data includes "owner" field
for identifier, location_dict in updated_values_dict.items():
if not isinstance(location_dict, dict):
continue
# Get owner directly from location_dict
owner_steamid = location_dict.get("owner")
if owner_steamid is None:
continue
location_id = f"{active_dataset}_{owner_steamid}_{identifier}"
# Get full location data from DOM if fields are missing in updated_values_dict
# (e.g., when only is_enabled is updated)
full_location_dict = (
module.dom.data
.get("module_locations", {})
.get("elements", {})
.get(active_dataset, {})
.get(owner_steamid, {})
.get(identifier, {})
)
coordinates = location_dict.get("coordinates")
if coordinates is None:
coordinates = full_location_dict.get("coordinates", {})
dimensions = location_dict.get("dimensions")
if dimensions is None:
dimensions = full_location_dict.get("dimensions", {})
location_data = {
"name": location_dict.get("name", full_location_dict.get("name", "Unknown")),
"identifier": identifier,
"owner": owner_steamid,
"shape": location_dict.get("shape", full_location_dict.get("shape", "circle")),
"coordinates": {
"x": float(coordinates.get("x", 0)),
"y": float(coordinates.get("y", 0)),
"z": float(coordinates.get("z", 0))
},
"dimensions": dimensions,
"teleport_entry": location_dict.get("teleport_entry", full_location_dict.get("teleport_entry", {})),
"type": location_dict.get("type", full_location_dict.get("type", [])),
"is_enabled": location_dict.get("is_enabled", full_location_dict.get("is_enabled", False))
}
module.webserver.send_data_to_client_hook(
module,
payload={
"location_id": location_id,
"location": location_data
},
data_type="location_update",
clients=[clientid]
)
elif method in ["remove"]:
# Send location removal
location_origin = updated_values_dict[2]
owner_steamid = updated_values_dict[3]
location_identifier = updated_values_dict[-1]
location_id = f"{location_origin}_{owner_steamid}_{location_identifier}"
module.webserver.send_data_to_client_hook(
module,
payload={
"location_id": location_id
},
data_type="location_remove",
clients=[clientid]
)
trigger_meta = {
"description": "sends location updates to webmap clients",
"main_function": send_location_update_to_map,
"handlers": {
"module_locations/elements/%map_identifier%/%owner_steamid%/%element_identifier%":
send_location_update_to_map,
}
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,61 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
player_name = regex_result.group("player_name")
command = regex_result.group("command")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
all_players_dict = (
module.dom.data
.get("module_players", {})
.get("elements", {})
.get(active_dataset)
)
steamid = None
servertime_player_died = "n/A"
for player_steamid, player_dict in all_players_dict.items():
if player_dict["name"] == player_name:
steamid = player_steamid
servertime_player_died = player_dict.get("last_seen_gametime", servertime_player_died)
break
if steamid is None:
return
if command == 'died':
event_data = ['edit_location', {
'location_coordinates': {
"x": player_dict["pos"]["x"],
"y": player_dict["pos"]["y"],
"z": player_dict["pos"]["z"]
},
'location_name': "Place of Death",
'action': 'edit',
'location_enabled': True,
'last_changed': servertime_player_died
}]
module.trigger_action_hook(origin_module, event_data=event_data, dispatchers_steamid=steamid)
trigger_meta = {
"description": "reacts to telnets player dying message",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["GMSG"]["command"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,63 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
if command != "EnterMultiplayer":
return
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
player_dict = {
"pos": {
"x": regex_result.group("pos_x"),
"y": regex_result.group("pos_y"),
"z": regex_result.group("pos_z")
}
}
player_steamid = regex_result.group("player_steamid")
first_seen_gametime_string = module.game_environment.get_last_recorded_gametime_string()
event_data = ['edit_location', {
'location_owner': player_steamid,
'location_coordinates': {
"x": player_dict["pos"]["x"],
"y": player_dict["pos"]["y"],
"z": player_dict["pos"]["z"]
},
'location_name': "Initial Spawn",
'action': 'create_new',
'location_enabled': True,
'last_changed': first_seen_gametime_string
}]
module.trigger_action_hook(origin_module, event_data=event_data)
trigger_meta = {
"description": "reacts to any initial playerspawn",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"PlayerSpawnedInWorld\s"
r"\("
r"reason: (?P<command>.+?),\s"
r"position: (?P<pos_x>.*),\s(?P<pos_y>.*),\s(?P<pos_z>.*)"
r"\):\s"
r"EntityID=(?P<entity_id>.*),\s"
r"PlayerID='(?P<player_steamid>.*)',\s"
r"OwnerID='(?P<owner_steamid>.*)',\s"
r"PlayerName='(?P<player_name>.*)'"
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,86 @@
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
position_dict = {
"pos": {
"x": regex_result.group("pos_x"),
"y": regex_result.group("pos_y"),
"z": regex_result.group("pos_z")
}
}
zombie_id = regex_result.group("entity_id")
zombie_name = regex_result.group("zombie_name")
screamer_safe_locations = []
found_screamer_safe_location = False
for screamer_safe_location in origin_module.get_elements_by_type("is_screamerfree"):
screamer_safe_locations.append(screamer_safe_location)
found_screamer_safe_location = True
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
if zombie_name == "zombieScreamer":
for location_dict in screamer_safe_locations:
if origin_module.locations.position_is_inside_boundary(position_dict, location_dict):
event_data = ['manage_entities', {
'dataset': active_dataset,
'entity_id': zombie_id,
'entity_name': zombie_name,
'action': 'kill'
}]
# no steamid cause it's a system_call
module.trigger_action_hook(origin_module.game_environment, event_data=event_data)
event_data = ['say_to_all', {
'message': (
'[FF6666]Screamer ([FFFFFF]{entity_id}[FF6666]) spawned[-] '
'[FFFFFF]inside [CCCCFF]{location_name}[FFFFFF].[-]'.format(
entity_id=zombie_id,
location_name=location_dict.get("name")
)
)
}]
module.trigger_action_hook(origin_module.game_environment, event_data=event_data)
# we only need to match one location. even though a screamer can be in multiple locations at once,
# we still only have to kill it once :)
break
else:
if found_screamer_safe_location:
event_data = ['say_to_all', {
'message': (
'[FF6666]Screamer ([FFFFFF]{entity_id}[FF6666]) spawned[-] '
'[CCCCFF]somewhere[FFFFFF]...[-]'.format(
entity_id=zombie_id
)
)
}]
module.trigger_action_hook(origin_module.game_environment, event_data=event_data)
trigger_meta = {
"description": "reacts to spawning zombies (screamers, mostly)",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"(?P<command>.+?)\s"
r"\["
r"type=(.*),\s"
r"name=(?P<zombie_name>.+?),\s"
r"id=(?P<entity_id>.*)\]\sat\s\((?P<pos_x>.*),\s(?P<pos_y>.*),\s(?P<pos_z>.*)\)\s"
r"Day=(\d.*)\s"
r"TotalInWave=(\d.*)\s"
r"CurrentWave=(\d.*)"
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

File diff suppressed because it is too large Load Diff