457 lines
17 KiB
Python
457 lines
17 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import json
|
|
import os
|
|
import math
|
|
|
|
|
|
def format_coord(num):
|
|
"""Format coordinate to 2 decimal places"""
|
|
return round(num, 2)
|
|
|
|
|
|
def calculate_distance(x1, y1, x2, y2):
|
|
"""Calculate Euclidean distance between two points"""
|
|
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
|
|
|
|
def find_nearby_node(nodes, x, y, node_type, max_distance=50):
|
|
"""Find a node of given type within specified distance"""
|
|
for node in nodes:
|
|
if node["type"] != node_type:
|
|
continue
|
|
|
|
node_x = node["position"]["x"]
|
|
node_y = node["position"]["y"]
|
|
|
|
if calculate_distance(node_x, node_y, x, y) < max_distance:
|
|
return node
|
|
|
|
return None
|
|
|
|
|
|
def parse_region_from_filename(filename):
|
|
"""Parse region from filename (e.g., "蒙德3-奔狼领-2.json" -> "蒙德3")"""
|
|
parts = filename.split('-')
|
|
if len(parts) < 2:
|
|
return None
|
|
return parts[0]
|
|
|
|
|
|
def extract_route_number(filename):
|
|
"""Extract route number and area from filename (e.g., "蒙德3-奔狼领-2.json" -> ("蒙德3", "奔狼领", 2))"""
|
|
parts = filename.split('-')
|
|
if len(parts) < 3:
|
|
return None, None, None
|
|
|
|
region = parts[0]
|
|
area = parts[1]
|
|
|
|
# Extract number from last part (remove .json)
|
|
num_part = parts[2].split('.')[0]
|
|
try:
|
|
num = int(num_part)
|
|
return region, area, num
|
|
except ValueError:
|
|
return region, area, None
|
|
|
|
|
|
def read_pathing_file(file_path):
|
|
"""Read a pathing file and return its data"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
print(f"Error reading {file_path}: {e}")
|
|
return None
|
|
|
|
|
|
def get_first_and_last_positions(path_data):
|
|
"""Extract first and last positions from pathing data"""
|
|
if not path_data or "positions" not in path_data:
|
|
return None, None
|
|
|
|
positions = path_data["positions"]
|
|
if not positions or len(positions) < 1: # Changed from 2 to 1 to handle single-position files
|
|
return None, None
|
|
|
|
return positions[0], positions[-1]
|
|
|
|
|
|
def generate_relative_path(file_path, script_dir):
|
|
"""Generate the appropriate relative path for routes"""
|
|
# Get the path relative to the script directory
|
|
relative_path = os.path.relpath(file_path, os.path.dirname(script_dir))
|
|
# Standardize path separator
|
|
relative_path = relative_path.replace("\\", "/")
|
|
|
|
# Remove the "AutoLeyLineOutcrop/" prefix if present
|
|
if relative_path.startswith("AutoLeyLineOutcrop/"):
|
|
relative_path = relative_path[len("AutoLeyLineOutcrop/"):]
|
|
|
|
return relative_path
|
|
|
|
|
|
def generate_ley_line_data():
|
|
"""Process pathing files and generate LeyLineOutcropData.json"""
|
|
# Path to the directory containing pathing files
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
pathing_dir = os.path.join(script_dir, "assets", "pathing")
|
|
|
|
print(f"Looking for pathing files in: {pathing_dir}")
|
|
|
|
# Initialize data structures
|
|
nodes = []
|
|
node_map = {} # Map to track nodes by ID
|
|
next_node_id = 1
|
|
|
|
# Used to group files by region and area
|
|
region_area_files = {}
|
|
file_data = {} # Store file data for each path
|
|
|
|
# Get all pathing JSON files (excluding rerun files and target directory)
|
|
pathing_files = []
|
|
target_files = {} # Map target files by region/area/num for reference
|
|
|
|
for root, _, files in os.walk(pathing_dir):
|
|
for file in files:
|
|
if not file.endswith('.json') or 'rerun' in file or 'rerun' in root:
|
|
continue
|
|
|
|
file_path = os.path.join(root, file)
|
|
|
|
# Process target directory files separately
|
|
if "target" in root.split(os.path.sep):
|
|
region, area, num = extract_route_number(file)
|
|
if region and area and num:
|
|
key = f"{region}-{area}-{num}"
|
|
target_files[key] = file_path
|
|
continue
|
|
|
|
pathing_files.append(file_path)
|
|
|
|
# Read and store file data
|
|
path_data = read_pathing_file(file_path)
|
|
if path_data:
|
|
file_data[file_path] = path_data
|
|
|
|
# Group files by region and area
|
|
region, area, num = extract_route_number(file)
|
|
if region and area and num:
|
|
key = f"{region}-{area}"
|
|
if key not in region_area_files:
|
|
region_area_files[key] = []
|
|
region_area_files[key].append((num, file_path))
|
|
|
|
# Sort files within each group by route number
|
|
for key in region_area_files:
|
|
region_area_files[key].sort() # Sort by route number
|
|
|
|
print(f"Found {len(pathing_files)} pathing files (excluding rerun and target files)")
|
|
|
|
# First pass: Process teleport points (routes that start with teleport)
|
|
for file_path, path_data in file_data.items():
|
|
first_pos, _ = get_first_and_last_positions(path_data)
|
|
if not first_pos:
|
|
continue
|
|
|
|
# Extract region from filename
|
|
file_name = os.path.basename(file_path)
|
|
region = parse_region_from_filename(file_name)
|
|
if not region:
|
|
continue
|
|
|
|
# Process teleport points
|
|
if first_pos.get("type") == "teleport":
|
|
x = format_coord(float(first_pos["x"]))
|
|
y = format_coord(float(first_pos["y"]))
|
|
|
|
# Check if we already have a nearby teleport node
|
|
teleport_node = find_nearby_node(nodes, x, y, "teleport")
|
|
|
|
if not teleport_node:
|
|
# Create new teleport node
|
|
teleport_node = {
|
|
"id": next_node_id,
|
|
"type": "teleport",
|
|
"region": region,
|
|
"position": {"x": x, "y": y},
|
|
"prev": [],
|
|
"next": []
|
|
}
|
|
nodes.append(teleport_node)
|
|
node_map[next_node_id] = teleport_node
|
|
next_node_id += 1
|
|
|
|
# Store teleport node in node_map
|
|
node_map[teleport_node["id"]] = teleport_node
|
|
|
|
# Build a map of first positions for path-type routes
|
|
# This will help us connect path routes to each other
|
|
path_first_positions = {}
|
|
for file_path, path_data in file_data.items():
|
|
first_pos, _ = get_first_and_last_positions(path_data)
|
|
if not first_pos or first_pos.get("type") != "path":
|
|
continue
|
|
|
|
file_name = os.path.basename(file_path)
|
|
region, area, num = extract_route_number(file_name)
|
|
if not region or not area or not num:
|
|
continue
|
|
|
|
key = f"{region}-{area}-{num}"
|
|
path_first_positions[key] = {
|
|
"x": format_coord(float(first_pos["x"])),
|
|
"y": format_coord(float(first_pos["y"])),
|
|
"file_path": file_path
|
|
}
|
|
|
|
# Second pass: Process all target points to create blossom nodes
|
|
# Also maintain a map from file info to target nodes
|
|
region_area_num_to_target = {}
|
|
|
|
for file_path, path_data in file_data.items():
|
|
_, last_pos = get_first_and_last_positions(path_data)
|
|
if not last_pos or last_pos.get("type") != "target":
|
|
continue
|
|
|
|
# Extract region from filename
|
|
file_name = os.path.basename(file_path)
|
|
region = parse_region_from_filename(file_name)
|
|
if not region:
|
|
continue
|
|
|
|
region, area, num = extract_route_number(file_name)
|
|
if not region or not area or not num:
|
|
continue
|
|
|
|
# Process target points as blossom nodes
|
|
target_x = format_coord(float(last_pos["x"]))
|
|
target_y = format_coord(float(last_pos["y"]))
|
|
|
|
# Check if we already have a nearby blossom node
|
|
blossom_node = find_nearby_node(nodes, target_x, target_y, "blossom")
|
|
|
|
if not blossom_node:
|
|
# Create new blossom node
|
|
blossom_node = {
|
|
"id": next_node_id,
|
|
"type": "blossom",
|
|
"region": region,
|
|
"position": {"x": target_x, "y": target_y},
|
|
"prev": [],
|
|
"next": []
|
|
}
|
|
nodes.append(blossom_node)
|
|
node_map[next_node_id] = blossom_node
|
|
next_node_id += 1
|
|
|
|
# Store the blossom node in our region-area-num map
|
|
key = f"{region}-{area}"
|
|
if key not in region_area_num_to_target:
|
|
region_area_num_to_target[key] = {}
|
|
region_area_num_to_target[key][num] = {
|
|
"node": blossom_node,
|
|
"file_path": file_path
|
|
}
|
|
|
|
# Special handling for files with only target positions (like 纳塔4-溶水域-2.json)
|
|
for file_path in pathing_files:
|
|
file_name = os.path.basename(file_path)
|
|
region, area, num = extract_route_number(file_name)
|
|
if not region or not area or not num:
|
|
continue
|
|
|
|
# Check if this file has target data but no blossom node yet
|
|
key = f"{region}-{area}"
|
|
if key in region_area_num_to_target and num not in region_area_num_to_target[key]:
|
|
# Check if we have a target file for this route
|
|
target_key = f"{region}-{area}-{num}"
|
|
if target_key in target_files:
|
|
target_path = target_files[target_key]
|
|
target_data = read_pathing_file(target_path)
|
|
|
|
if target_data and "positions" in target_data and target_data["positions"]:
|
|
# Create blossom node from the target file
|
|
target_pos = target_data["positions"][0]
|
|
if target_pos.get("type") == "target":
|
|
target_x = format_coord(float(target_pos["x"]))
|
|
target_y = format_coord(float(target_pos["y"]))
|
|
|
|
# Check if we already have a nearby blossom node
|
|
blossom_node = find_nearby_node(nodes, target_x, target_y, "blossom")
|
|
|
|
if not blossom_node:
|
|
# Create new blossom node
|
|
blossom_node = {
|
|
"id": next_node_id,
|
|
"type": "blossom",
|
|
"region": region,
|
|
"position": {"x": target_x, "y": target_y},
|
|
"prev": [],
|
|
"next": []
|
|
}
|
|
nodes.append(blossom_node)
|
|
node_map[next_node_id] = blossom_node
|
|
next_node_id += 1
|
|
|
|
# Add to region_area_num_to_target map
|
|
if key not in region_area_num_to_target:
|
|
region_area_num_to_target[key] = {}
|
|
region_area_num_to_target[key][num] = {
|
|
"node": blossom_node,
|
|
"file_path": file_path
|
|
}
|
|
|
|
# Third pass: Connect teleport points to their target blossoms
|
|
for file_path, path_data in file_data.items():
|
|
first_pos, last_pos = get_first_and_last_positions(path_data)
|
|
if not first_pos or not last_pos:
|
|
continue
|
|
|
|
# Skip if this isn't a valid target
|
|
if last_pos.get("type") != "target":
|
|
continue
|
|
|
|
# Extract file info
|
|
file_name = os.path.basename(file_path)
|
|
region, area, num = extract_route_number(file_name)
|
|
|
|
# Skip if we can't parse the file name
|
|
if not region or not area or not num:
|
|
continue
|
|
|
|
# For teleport source type, connect to target
|
|
if first_pos.get("type") == "teleport":
|
|
# Find teleport node
|
|
x = format_coord(float(first_pos["x"]))
|
|
y = format_coord(float(first_pos["y"]))
|
|
teleport_node = find_nearby_node(nodes, x, y, "teleport")
|
|
|
|
# Find target blossom node
|
|
target_x = format_coord(float(last_pos["x"]))
|
|
target_y = format_coord(float(last_pos["y"]))
|
|
target_node = find_nearby_node(nodes, target_x, target_y, "blossom")
|
|
|
|
if teleport_node and target_node:
|
|
# Add connection
|
|
relative_path = generate_relative_path(file_path, script_dir)
|
|
|
|
# Add target to teleport's next array if not already there
|
|
route_exists = False
|
|
for route in teleport_node["next"]:
|
|
if route["target"] == target_node["id"]:
|
|
route_exists = True
|
|
break
|
|
|
|
if not route_exists:
|
|
teleport_node["next"].append({
|
|
"target": target_node["id"],
|
|
"route": relative_path
|
|
})
|
|
|
|
# Add teleport to target's prev array if not already there
|
|
if teleport_node["id"] not in target_node["prev"]:
|
|
target_node["prev"].append(teleport_node["id"])
|
|
|
|
# Fourth pass: Connect nodes based on numerical sequence
|
|
for region_area, num_to_target in region_area_num_to_target.items():
|
|
route_numbers = sorted(num_to_target.keys())
|
|
|
|
# Connect each route to the next numerically
|
|
for i in range(len(route_numbers) - 1):
|
|
current_num = route_numbers[i]
|
|
next_num = route_numbers[i + 1]
|
|
|
|
current_info = num_to_target[current_num]
|
|
next_info = num_to_target[next_num]
|
|
|
|
current_node = current_info["node"]
|
|
next_node = next_info["node"]
|
|
next_file_path = next_info["file_path"]
|
|
|
|
# Create the connection
|
|
relative_path = generate_relative_path(next_file_path, script_dir)
|
|
|
|
# Add next node to current's next array if not already there
|
|
route_exists = False
|
|
for route in current_node["next"]:
|
|
if route["target"] == next_node["id"]:
|
|
route_exists = True
|
|
break
|
|
|
|
if not route_exists:
|
|
current_node["next"].append({
|
|
"target": next_node["id"],
|
|
"route": relative_path
|
|
})
|
|
|
|
# Add current node to next's prev array if not already there
|
|
if current_node["id"] not in next_node["prev"]:
|
|
next_node["prev"].append(current_node["id"])
|
|
|
|
# Fifth pass: Connect "path" type sources to their targets
|
|
for file_path, path_data in file_data.items():
|
|
first_pos, last_pos = get_first_and_last_positions(path_data)
|
|
if not first_pos or not last_pos:
|
|
continue
|
|
|
|
# Skip if not a path type or not a valid target
|
|
if first_pos.get("type") != "path" or last_pos.get("type") != "target":
|
|
continue
|
|
|
|
# Extract file info
|
|
file_name = os.path.basename(file_path)
|
|
region, area, num = extract_route_number(file_name)
|
|
|
|
# Skip if we can't parse the file name
|
|
if not region or not area or not num:
|
|
continue
|
|
|
|
# Try to find the source in previous route target
|
|
prev_num = num - 1
|
|
key = f"{region}-{area}"
|
|
if key in region_area_num_to_target and prev_num in region_area_num_to_target[key]:
|
|
prev_info = region_area_num_to_target[key][prev_num]
|
|
prev_node = prev_info["node"]
|
|
|
|
# Find the current target node
|
|
target_x = format_coord(float(last_pos["x"]))
|
|
target_y = format_coord(float(last_pos["y"]))
|
|
target_node = find_nearby_node(nodes, target_x, target_y, "blossom")
|
|
|
|
if prev_node and target_node:
|
|
# Add connection from previous target to current target
|
|
relative_path = generate_relative_path(file_path, script_dir)
|
|
|
|
# Add target to previous node's next array if not already there
|
|
route_exists = False
|
|
for route in prev_node["next"]:
|
|
if route["target"] == target_node["id"]:
|
|
route_exists = True
|
|
break
|
|
|
|
if not route_exists:
|
|
prev_node["next"].append({
|
|
"target": target_node["id"],
|
|
"route": relative_path
|
|
})
|
|
|
|
# Add previous node to target's prev array if not already there
|
|
if prev_node["id"] not in target_node["prev"]:
|
|
target_node["prev"].append(prev_node["id"])
|
|
|
|
# Save to JSON file
|
|
ley_line_data = {"node": nodes}
|
|
output_path = os.path.join(script_dir, "LeyLineOutcropData.json")
|
|
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
json.dump(ley_line_data, f, ensure_ascii=False, indent=4)
|
|
|
|
print(f"Generated LeyLineOutcropData.json with {len(nodes)} nodes")
|
|
return ley_line_data
|
|
|
|
|
|
if __name__ == "__main__":
|
|
generate_ley_line_data() |