38676-vm/gm_console.php
Flatlogic Bot 9810d7fd7c Alpha V2.1
2026-02-26 22:49:47 +00:00

822 lines
43 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once 'db/config.php';
require_once 'includes/status_helper.php';
session_start();
$db = db();
// Auth Check: Must be logged in and be admin or gm
if (!isset($_SESSION['user_id'])) {
header("Location: auth.php?page=login");
exit;
}
$user_id = $_SESSION['user_id'];
$user_stmt = $db->prepare("SELECT role FROM users WHERE id = ?");
$user_stmt->execute([$user_id]);
$current_user = $user_stmt->fetch();
if (!$current_user || ($current_user['role'] !== 'admin' && $current_user['role'] !== 'gm')) {
die("Accès refusé. Vous devez être un Maître du Jeu (MJ) pour accéder à cette console.");
}
$is_admin = ($current_user['role'] === 'admin');
// Fetch Dynamic Types, Statuses, Settlement Types, and Factions
$object_types_db = $db->query("SELECT * FROM celestial_object_types ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
$statuses_db = $db->query("SELECT * FROM celestial_object_statuses ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
$settlement_types_db = $db->query("SELECT * FROM settlement_types ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
$factions_db = $db->query("SELECT * FROM factions ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
$object_types_map = []; foreach($object_types_db as $ot) $object_types_map[$ot['slug']] = $ot;
$statuses_map = []; foreach($statuses_db as $s) $statuses_map[$s['slug']] = $s;
$factions_map = []; foreach($factions_db as $f) $factions_map[$f['id']] = $f;
// Handle Planet/Slot Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_slot') {
$slot_id = (int)($_POST['slot_id'] ?? 0);
$galaxy_id = (int)($_POST['galaxy_id'] ?? 1);
$sector_id = (int)($_POST['sector_id'] ?? 1);
$slot_num = (int)($_POST['slot_num'] ?? 0);
$name = $_POST['name'] ?? 'Inconnu';
$type = $_POST['type'] ?? 'empty';
$manual_status = $_POST['manual_status'] ?? '';
// Orbital control is now detailed by faction
$orbital_controls = $_POST['orbital_controls'] ?? [];
$dominant_orbital_val = 0;
$dominant_orbital_faction = null;
foreach($orbital_controls as $fid => $val) {
if ((int)$val > $dominant_orbital_val && (int)$fid != 1) { // Not "Aucune"
$dominant_orbital_val = (int)$val;
$dominant_orbital_faction = (int)$fid;
}
}
// Status is now 'sta_auto' by default, overridden only by manual MJ selection
// Dynamic status is calculated on the fly in the helper
$status = 'sta_auto';
$faction_id = null;
$total_non_aucun = 0;
$active_factions = [];
$num_cities = 0;
$avg_terrestrial_control = 0;
if (isset($_POST['cities']) && is_array($_POST['cities'])) {
foreach ($_POST['cities'] as $city_data) {
if (empty($city_data['name'])) continue;
$num_cities++;
if (isset($city_data['controls']) && is_array($city_data['controls'])) {
foreach ($city_data['controls'] as $f_id => $lvl) {
$lvl = (int)$lvl;
if ($lvl > 0 && $f_id != 1) { // 1 is "Aucune"
$total_non_aucun += $lvl;
$active_factions[$f_id] = ($active_factions[$f_id] ?? 0) + $lvl;
$avg_terrestrial_control += $lvl;
}
}
}
}
}
if ($num_cities > 0) {
$avg_terrestrial_control = round($avg_terrestrial_control / $num_cities);
}
if ($num_cities > 0 && $total_non_aucun > 0) {
arsort($active_factions);
$faction_id = (int)key($active_factions);
}
// Manual status override if specified by MJ
if (!empty($manual_status)) {
$status = $manual_status;
}
if ($type === 'empty') {
if ($slot_id > 0) {
$db->prepare("DELETE FROM cities WHERE planet_id = ?")->execute([$slot_id]);
$db->prepare("DELETE FROM planet_faction_control WHERE planet_id = ?")->execute([$slot_id]);
$db->prepare("DELETE FROM planets WHERE id = ?")->execute([$slot_id]);
}
} else {
if ($slot_id > 0) {
$stmt = $db->prepare("UPDATE planets SET name = ?, type = ?, status = ?, faction_id = ?, orbital_control = ?, terrestrial_control = ? WHERE id = ?");
$stmt->execute([$name, $type, $status, $faction_id, $dominant_orbital_val, $avg_terrestrial_control, $slot_id]);
$planet_id = $slot_id;
} else {
$stmt = $db->prepare("INSERT INTO planets (galaxy_id, sector_id, slot, name, type, status, faction_id, orbital_control, terrestrial_control) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$galaxy_id, $sector_id, $slot_num, $name, $type, $status, $faction_id, $dominant_orbital_val, $avg_terrestrial_control]);
$planet_id = $db->lastInsertId();
}
// Handle Orbital Faction Control
$db->prepare("DELETE FROM planet_faction_control WHERE planet_id = ?")->execute([$planet_id]);
foreach($orbital_controls as $fid => $lvl) {
if ((int)$lvl > 0) {
$db->prepare("INSERT INTO planet_faction_control (planet_id, faction_id, control_level) VALUES (?, ?, ?)")->execute([$planet_id, (int)$fid, (int)$lvl]);
}
}
// Handle Multiple Settlements
$sent_city_ids = [];
if (isset($_POST['cities']) && is_array($_POST['cities'])) {
foreach ($_POST['cities'] as $city_data) {
if (empty($city_data['name'])) continue;
$c_id = (int)($city_data['id'] ?? 0);
$c_name = $city_data['name'];
$c_type_id = !empty($city_data['type_id']) ? (int)$city_data['type_id'] : null;
if ($c_id > 0) {
$stmt = $db->prepare("UPDATE cities SET name = ?, settlement_type_id = ? WHERE id = ? AND planet_id = ?");
$stmt->execute([$c_name, $c_type_id, $c_id, $planet_id]);
$city_id = $c_id;
} else {
$stmt = $db->prepare("INSERT INTO cities (planet_id, name, settlement_type_id) VALUES (?, ?, ?)");
$stmt->execute([$planet_id, $c_name, $c_type_id]);
$city_id = $db->lastInsertId();
}
$sent_city_ids[] = $city_id;
// Handle Faction Control
$db->prepare("DELETE FROM city_faction_control WHERE city_id = ?")->execute([$city_id]);
if (isset($city_data['controls']) && is_array($city_data['controls'])) {
foreach ($city_data['controls'] as $fac_id => $control_lvl) {
$control_lvl = (int)$control_lvl;
if ($control_lvl > 0) {
$stmt = $db->prepare("INSERT INTO city_faction_control (city_id, faction_id, control_level) VALUES (?, ?, ?)");
$stmt->execute([$city_id, (int)$fac_id, $control_lvl]);
}
}
}
}
}
if ($planet_id > 0) {
if (empty($sent_city_ids)) {
$db->prepare("DELETE FROM cities WHERE planet_id = ?")->execute([$planet_id]);
} else {
$placeholders = implode(',', array_fill(0, count($sent_city_ids), '?'));
$stmt = $db->prepare("DELETE FROM cities WHERE planet_id = ? AND id NOT IN ($placeholders)");
$params = array_merge([$planet_id], $sent_city_ids);
$stmt->execute($params);
}
}
}
header("Location: gm_console.php?view=sector&galaxy_id=$galaxy_id&sector_id=$sector_id&success=1");
exit;
}
// Handle Sector Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_sector') {
$sector_id = (int)$_POST['sector_id'];
$galaxy_id = (int)$_POST['galaxy_id'];
$s_name = $_POST['sector_name'] ?? "Secteur $sector_id";
$s_status = $_POST['sector_status'] ?? 'unexplored';
$stmt = $db->prepare("INSERT INTO sectors (id, galaxy_id, name, status) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE name = ?, status = ?");
$stmt->execute([$sector_id, $galaxy_id, $s_name, $s_status, $s_name, $s_status]);
header("Location: gm_console.php?view=sector&galaxy_id=$galaxy_id&sector_id=$sector_id&success=1");
exit;
}
$view = isset($_GET['view']) ? $_GET['view'] : 'galaxy';
$galaxy_id = isset($_GET['galaxy_id']) ? (int)$_GET['galaxy_id'] : 1;
$sector_id = isset($_GET['sector_id']) ? (int)$_GET['sector_id'] : 1;
$grid_size = 36;
if ($view === 'sector') {
$stmt = $db->prepare("SELECT * FROM planets WHERE galaxy_id = ? AND sector_id = ? AND slot BETWEEN 1 AND ?");
$stmt->execute([$galaxy_id, $sector_id, $grid_size]);
$objects_raw = $stmt->fetchAll();
$grid = array_fill(1, $grid_size, null);
$planet_ids = [];
foreach ($objects_raw as $obj) {
$grid[$obj['slot']] = $obj;
$planet_ids[] = $obj['id'];
$grid[$obj['slot']]['cities'] = [];
$grid[$obj['slot']]['orbital_controls'] = [];
$grid[$obj['slot']]['terrestrial_controls'] = [];
}
if (!empty($planet_ids)) {
// Fetch Orbital Controls
$placeholders = implode(',', array_fill(0, count($planet_ids), '?'));
$stmt = $db->prepare("SELECT * FROM planet_faction_control WHERE planet_id IN ($placeholders)");
$stmt->execute($planet_ids);
$orb_controls_raw = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($orb_controls_raw as $ocr) {
foreach ($grid as &$slot_data) {
if ($slot_data && $slot_data['id'] == $ocr['planet_id']) {
$slot_data['orbital_controls'][$ocr['faction_id']] = $ocr['control_level'];
}
}
}
// Fetch Cities
unset($slot_data);
$stmt = $db->prepare("SELECT * FROM cities WHERE planet_id IN ($placeholders)");
$stmt->execute($planet_ids);
$all_cities = $stmt->fetchAll(PDO::FETCH_ASSOC);
$city_ids = array_column($all_cities, 'id');
$city_controls = [];
if (!empty($city_ids)) {
$c_placeholders = implode(',', array_fill(0, count($city_ids), '?'));
$c_stmt = $db->prepare("SELECT * FROM city_faction_control WHERE city_id IN ($c_placeholders)");
$c_stmt->execute($city_ids);
$controls_raw = $c_stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($controls_raw as $cr) {
$city_controls[$cr['city_id']][$cr['faction_id']] = $cr['control_level'];
}
}
$planet_terrestrial_agg = [];
foreach ($all_cities as $city) {
$pid = $city['planet_id'];
$city['controls'] = $city_controls[$city['id']] ?? [];
foreach ($city['controls'] as $fid => $lvl) {
$planet_terrestrial_agg[$pid][$fid] = ($planet_terrestrial_agg[$pid][$fid] ?? 0) + $lvl;
}
foreach ($grid as &$slot_data) {
if ($slot_data && $slot_data['id'] == $city['planet_id']) {
$slot_data['cities'][] = $city;
}
}
}
// Aggregate terrestrial controls
foreach ($grid as &$slot_data) {
if ($slot_data && !empty($slot_data['cities'])) {
$num_cities = count($slot_data['cities']);
if (isset($planet_terrestrial_agg[$slot_data['id']])) {
foreach ($planet_terrestrial_agg[$slot_data['id']] as $fid => $total) {
$slot_data['terrestrial_controls'][$fid] = round($total / $num_cities);
}
}
}
}
unset($slot_data);
// Apply dynamic status
foreach ($grid as &$slot_data) {
if ($slot_data) {
$slot_data['status'] = calculateCelestialStatus($slot_data, $db, $statuses_map);
}
}
unset($slot_data);
}
$stmt = $db->prepare("SELECT name, status FROM sectors WHERE id = ?");
$stmt->execute([$sector_id]);
$sector_info = $stmt->fetch();
} else { // Galaxy view
$stmt = $db->prepare("SELECT * FROM planets WHERE galaxy_id = ? ORDER BY sector_id, slot ASC");
$stmt->execute([$galaxy_id]);
$all_planets = $stmt->fetchAll(PDO::FETCH_ASSOC);
$planet_ids = array_column($all_planets, 'id');
$orb_controls = [];
$terr_controls = [];
$city_counts = [];
if (!empty($planet_ids)) {
$placeholders = implode(',', array_fill(0, count($planet_ids), '?'));
// Orbital
$o_stmt = $db->prepare("SELECT * FROM planet_faction_control WHERE planet_id IN ($placeholders)");
$o_stmt->execute($planet_ids);
while($r = $o_stmt->fetch()) $orb_controls[$r['planet_id']][$r['faction_id']] = $r['control_level'];
// Terrestrial (Aggregated per planet)
$t_stmt = $db->prepare("SELECT c.planet_id, cfc.faction_id, SUM(cfc.control_level) as total_lvl
FROM city_faction_control cfc
JOIN cities c ON cfc.city_id = c.id
WHERE c.planet_id IN ($placeholders)
GROUP BY c.planet_id, cfc.faction_id");
$t_stmt->execute($planet_ids);
while($r = $t_stmt->fetch()) {
$terr_controls[$r['planet_id']][$r['faction_id']] = $r['total_lvl'];
}
// City counts
$c_stmt = $db->prepare("SELECT planet_id, COUNT(*) as cnt FROM cities WHERE planet_id IN ($placeholders) GROUP BY planet_id");
$c_stmt->execute($planet_ids);
while($r = $c_stmt->fetch()) $city_counts[$r['planet_id']] = $r['cnt'];
}
$sector_data = [];
$active_sectors = [];
foreach ($all_planets as $p) {
$p['orbital_controls'] = $orb_controls[$p['id']] ?? [];
$p['cities'] = isset($city_counts[$p['id']]) ? array_fill(0, $city_counts[$p['id']], []) : [];
$p['terrestrial_controls'] = [];
if (!empty($p['cities'])) {
$num_cities = count($p['cities']);
if (isset($terr_controls[$p['id']])) {
foreach ($terr_controls[$p['id']] as $fid => $total_lvl) {
$p['terrestrial_controls'][$fid] = round($total_lvl / $num_cities);
}
}
}
$dynamic_status = calculateCelestialStatus($p, $db, $statuses_map);
$sector_data[$p['sector_id']][$p['slot']] = ['status' => $dynamic_status, 'type' => $p['type']];
if (!in_array($p['sector_id'], $active_sectors)) { $active_sectors[] = (int)$p['sector_id']; }
}
}
function getStatusColor($status, $type, $statuses_map, $object_types_map) {
if ($type === 'empty') return 'rgba(255,255,255,0.05)';
$c = $statuses_map[$status]['color'] ?? 'rgba(255,255,255,0.05)'; return str_replace(';blink', '', $c);
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Console MJ - Nexus</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="assets/css/custom.css?v=<?php echo time(); ?>" rel="stylesheet">
<style>
body { background: #0b0f19; color: #fff; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; }
header { background: #1a202c; padding: 10px 20px; border-bottom: 2px solid #2d3545; display: flex; justify-content: space-between; align-items: center; }
.container { padding: 40px; display: flex; flex-direction: column; align-items: center; }
.galaxy-map {
display: grid;
grid-template-columns: repeat(6, 140px);
grid-template-rows: repeat(6, 140px);
gap: 10px;
padding: 15px;
background: rgba(10, 15, 30, 0.5);
border: 1px solid #2d3545;
}
.slot {
width: 140px;
height: 140px;
background: rgba(46, 52, 64, 0.3);
border: 1px solid #3b4252;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
overflow: hidden;
}
.slot:hover { background: rgba(136, 192, 208, 0.1); border-color: #88c0d0; }
.slot-id { position: absolute; top: 5px; left: 8px; font-size: 9px; color: #4c566a; font-weight: bold; z-index: 5; }
.slot-icons {
position: absolute;
top: 5px;
right: 5px;
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
z-index: 6;
}
.faction-icon-sm {
width: 22px;
height: 22px;
filter: drop-shadow(0 0 2px rgba(0,0,0,0.8));
display: flex;
align-items: center;
justify-content: center;
}
.info-icon-sm {
width: 20px;
height: 20px;
font-size: 14px;
color: #ebcb8b;
filter: drop-shadow(0 0 2px rgba(0,0,0,0.8));
display: flex;
align-items: center;
justify-content: center;
}
.object-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90px;
height: 90px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
font-size: 90px;
z-index: 2;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.object-image { width: 90px; height: 90px; object-fit: contain; margin: 0; }
.slot:hover .object-icon { transform: translate(-50%, -50%) scale(1.1); }
.object-name {
position: absolute;
bottom: 8px;
font-size: 11px;
font-weight: bold;
color: #eceff4;
text-align: center;
width: 95%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
z-index: 3;
text-shadow: 0 0 4px rgba(0,0,0,0.8);
}
#editModal, #sectorModal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 1000; align-items: center; justify-content: center; }
.modal-content { background: #1e293b; padding: 30px; border: 1px solid #88c0d0; width: 650px; max-height: 90vh; overflow-y: auto; border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; font-size: 12px; color: #8c92a3; margin-bottom: 8px; font-weight: bold; }
.form-group input, .form-group select, .form-group textarea { width: 100%; background: #0f172a; border: 1px solid #334155; color: #fff; padding: 10px; box-sizing: border-box; border-radius: 4px; font-size: 14px; }
.btn-save { background: #a3be8c; border: none; padding: 12px 25px; color: #000; font-weight: bold; cursor: pointer; border-radius: 4px; font-size: 14px; width: 100%; }
.btn-cancel { background: #4c566a; border: none; padding: 12px 25px; color: #fff; font-weight: bold; cursor: pointer; border-radius: 4px; font-size: 14px; width: 100%; margin-top: 10px; }
.settlement-item { background: #1e293b; border: 1px solid #334155; padding: 15px; margin-bottom: 10px; position: relative; border-radius: 6px; }
.btn-remove-settlement { position: absolute; right: 8px; top: 8px; background: #bf616a; color: #fff; border: none; width: 22px; height: 22px; cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; }
.btn-add-settlement { background: #81a1c1; color: #000; border: none; padding: 8px 15px; cursor: pointer; font-size: 11px; font-weight: bold; border-radius: 4px; width: 100%; margin-bottom: 15px; transition: 0.2s; }
.btn-add-settlement:hover { background: #88c0d0; }
.compact-row { display: flex; gap: 15px; align-items: flex-end; margin-bottom: 15px; }
.compact-row .form-group { margin-bottom: 0; }
.control-bars { margin-top: 15px; display: flex; flex-direction: column; gap: 10px; padding-top: 10px; border-top: 1px dashed #334155; }
.control-bar-row { display: flex; align-items: center; gap: 10px; }
.control-bar-label { width: 100px; font-size: 11px; color: #eceff4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 5px; }
.control-bar-input { flex: 1; -webkit-appearance: none; height: 8px; background: #0f172a; border-radius: 4px; outline: none; }
.control-bar-input::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: #88c0d0; border-radius: 50%; cursor: pointer; }
.control-bar-value { width: 35px; text-align: right; font-size: 11px; color: #88c0d0; font-weight: bold; }
.sector-grid {
display: grid;
grid-template-columns: repeat(6, 180px);
grid-template-rows: repeat(6, 180px);
gap: 15px;
}
.sector-card { background: rgba(10, 15, 30, 0.95); border: 1px solid #2d3545; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-decoration: none; color: #fff; transition: all 0.2s; width: 180px; height: 180px; box-sizing: border-box; }
.sector-card:hover { border-color: #88c0d0; background: #1a202c; transform: translateY(-3px); }
.mini-map { display: grid; grid-template-columns: repeat(6, 12px); gap: 4px; margin-bottom: 10px; background: #000; padding: 6px; }
.mini-dot { width: 12px; height: 12px; border-radius: 1px; }
.blink-effect { animation: blinker 1.5s linear infinite; }
@keyframes blinker { 50% { opacity: 0.3; } }
.faction-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.legend { margin-top: 30px; background: rgba(10, 15, 30, 0.95); border: 1px solid #2d3545; padding: 15px 25px; display: flex; gap: 20px; font-size: 11px; flex-wrap: wrap; max-width: 1000px; justify-content: center; border-radius: 8px; }
.legend-item { display: flex; align-items: center; gap: 8px; color: #8c92a3; }
.dot { width: 10px; height: 10px; border-radius: 2px; }
</style>
</head>
<body>
<header>
<div style="display: flex; align-items: center; gap: 20px;">
<h2 style="margin: 0; color: #ebcb8b;"><i class="fa-solid fa-headset"></i> CONSOLE MJ</h2>
<nav style="display: flex; gap: 20px;">
<a href="project_log.php" style="color: #ebcb8b; text-decoration: none; font-size: 14px; font-weight: bold;"><i class="fa-solid fa-clipboard-list"></i> Journal</a> <a href="index.php" style="color: #88c0d0; text-decoration: none; font-size: 14px; font-weight: bold;"><i class="fa-solid fa-eye"></i> Vue Joueur</a>
<?php if ($is_admin): ?>
<a href="admin.php" style="color: #bf616a; text-decoration: none; font-size: 14px; font-weight: bold;"><i class="fa-solid fa-shield-halved"></i> Console Admin</a>
<?php endif; ?>
</nav>
</div>
<div style="font-size: 14px;">Connecté en tant que MJ: <strong style="color: #ebcb8b;">@<?php echo htmlspecialchars($_SESSION['username'] ?? 'MJ'); ?></strong></div>
</header>
<div class="container">
<?php if (isset($_GET["success"])): ?>
<div style="background: rgba(163, 190, 140, 0.2); border: 1px solid #a3be8c; color: #a3be8c; padding: 15px; border-radius: 4px; margin-bottom: 20px; width: 100%; max-width: 840px; text-align: center;">
<i class="fa-solid fa-circle-check"></i> Modifications enregistrées avec succès !
</div>
<?php endif; ?>
<?php if ($view === 'galaxy'): ?>
<h3 style="color: #88c0d0; margin-bottom: 30px;">Navigateur de Galaxie</h3>
<div class="sector-grid">
<?php for($s=1; $s<=$grid_size; $s++): ?>
<a href="?view=sector&galaxy_id=<?php echo $galaxy_id; ?>&sector_id=<?php echo $s; ?>" class="sector-card">
<div class="mini-map">
<?php for($p=1; $p<=$grid_size; $p++):
$dotColor = 'rgba(255,255,255,0.05)';
if (isset($sector_data[$s][$p])) { $dotColor = getStatusColor($sector_data[$s][$p]['status'], $sector_data[$s][$p]['type'], $statuses_map, $object_types_map); }
?>
<div class="mini-dot <?php echo (isset($sector_data[$s][$p]) && ($statuses_map[$sector_data[$s][$p]["status"]]["is_blinking"] ?? 0)) ? "blink-effect" : ""; ?>" style="background-color: <?php echo $dotColor; ?>;"></div>
<?php endfor; ?>
</div>
<div style="font-size: 14px; font-weight: bold; margin-top: 5px;">SECTEUR <?php echo $s; ?></div>
</a>
<?php endfor; ?>
</div>
<?php elseif ($view === 'sector'): ?>
<div style="display: flex; justify-content: space-between; width: 100%; max-width: 840px; align-items: center; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 15px;">
<a href="?view=galaxy" style="color: #88c0d0; text-decoration: none;"><i class="fa-solid fa-arrow-left"></i> Retour</a>
<h3 style="color: #88c0d0; margin: 0;">Secteur <?php echo $sector_id; ?>: <?php echo htmlspecialchars($sector_info['name'] ?? "Secteur $sector_id"); ?></h3>
</div>
<button onclick="editSector()" style="background: #5e81ac; border: none; color: #fff; padding: 8px 15px; cursor: pointer; font-size: 12px; font-weight: bold; border-radius: 4px;"><i class="fa-solid fa-pen-to-square"></i> MODIFIER SECTEUR</button>
</div>
<div class="galaxy-map">
<?php for($i=1; $i<=$grid_size; $i++): ?>
<div class="slot" onclick='editSlot(<?php echo $i; ?>, <?php echo json_encode($grid[$i] ?? null); ?>)'>
<span class="slot-id"><?php echo $i; ?></span>
<?php if (isset($grid[$i])): $obj = $grid[$i];
$type_info = $object_types_map[$obj['type']] ?? null;
$fac_info = isset($obj['faction_id']) ? ($factions_map[$obj['faction_id']] ?? null) : null;
?>
<div class="slot-icons">
<?php if ($fac_info): ?>
<div class="faction-icon-sm">
<?php if (!empty($fac_info['image_url'])): ?>
<img src="<?php echo htmlspecialchars($fac_info['image_url']); ?>?v=<?php echo time(); ?>" style="width: 100%; height: 100%; object-fit: contain;" title="<?php echo htmlspecialchars($fac_info['name']); ?>">
<?php elseif (!empty($fac_info['fa_icon'])): ?>
<i class="fa-solid <?php echo htmlspecialchars($fac_info['fa_icon']); ?>" style="color: <?php echo htmlspecialchars($fac_info['color'] ?? '#fff'); ?>; font-size: 16px;" title="<?php echo htmlspecialchars($fac_info['name']); ?>"></i>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($obj['cities'])): ?>
<div class="info-icon-sm" title="Établissements présents">
<i class="fa-solid fa-city"></i>
</div>
<?php endif; ?>
</div>
<div class="object-icon <?php echo ($statuses_map[$obj['status']]['is_blinking'] ?? 0) ? 'blink-effect' : ''; ?>">
<?php
$icon = $type_info['icon'] ?? 'fa-circle';
$color = getStatusColor($obj['status'], $obj['type'], $statuses_map, $object_types_map);
$imageUrl = $type_info['image_url'] ?? null;
?>
<?php if ($imageUrl): ?>
<img src="<?php echo htmlspecialchars($imageUrl); ?>?v=<?php echo time(); ?>" class="object-image">
<?php else: ?>
<i class="fa-solid <?php echo $icon; ?>" style="color: <?php echo $color; ?>;"></i>
<?php endif; ?>
</div>
<span class="object-name"><?php echo htmlspecialchars($obj['name']); ?></span>
<?php else: ?>
<div style="opacity: 0.1;"><i class="fa-solid fa-circle fa-sm"></i></div>
<?php endif; ?>
</div>
<?php endfor; ?>
</div>
<?php endif; ?>
<div class="legend">
<?php foreach($statuses_db as $s): ?>
<div class="legend-item">
<span class="dot <?php echo $s["is_blinking"] ? 'blink-effect' : ''; ?>" style="background: <?php echo str_replace('\;blink', '', str_replace(' ;blink', '', $s['color'])); ?>;"></span>
<?php echo htmlspecialchars($s["name"]); ?>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- SECTOR EDIT MODAL -->
<div id="sectorModal">
<div class="modal-content">
<h3 style="color: #88c0d0; margin-top: 0;">Paramètres du Secteur <?php echo $sector_id; ?></h3>
<form method="POST">
<input type="hidden" name="action" value="update_sector">
<input type="hidden" name="sector_id" value="<?php echo $sector_id; ?>">
<input type="hidden" name="galaxy_id" value="<?php echo $galaxy_id; ?>">
<div class="form-group">
<label>Nom du Secteur</label>
<input type="text" name="sector_name" value="<?php echo htmlspecialchars($sector_info['name'] ?? "Secteur $sector_id"); ?>">
</div>
<div class="form-group">
<label>État Global</label>
<select name="sector_status">
<option value="unexplored" <?php echo ($sector_info['status'] ?? '') === 'unexplored' ? 'selected' : ''; ?>>Inexploré</option>
<option value="neutral" <?php echo ($sector_info['status'] ?? '') === 'neutral' ? 'selected' : ''; ?>>Neutre</option>
<option value="contested" <?php echo ($sector_info['status'] ?? '') === 'contested' ? 'selected' : ''; ?>>Contesté</option>
<option value="controlled" <?php echo ($sector_info['status'] ?? '') === 'controlled' ? 'selected' : ''; ?>>Sous Contrôle</option>
<option value="hostile" <?php echo ($sector_info['status'] ?? '') === 'hostile' ? 'selected' : ''; ?>>Hostile</option>
</select>
</div>
<button type="submit" class="btn-save">ENREGISTRER LE SECTEUR</button>
<button type="button" class="btn-cancel" onclick="closeSectorModal()">ANNULER</button>
</form>
</div>
</div>
<!-- SLOT EDIT MODAL -->
<div id="editModal">
<div class="modal-content">
<h3 id="modalTitle" style="color: #88c0d0; margin-top: 0;">Édition de la case</h3>
<form method="POST">
<input type="hidden" name="action" value="update_slot">
<input type="hidden" name="slot_id" id="slot_id">
<input type="hidden" name="galaxy_id" value="<?php echo $galaxy_id; ?>">
<input type="hidden" name="sector_id" value="<?php echo $sector_id; ?>">
<input type="hidden" name="slot_num" id="slot_num">
<div class="compact-row">
<div class="form-group" style="flex: 2;">
<label>Nom de l'objet</label>
<input type="text" name="name" id="field_name" required>
</div>
<div class="form-group" style="flex: 1;">
<label>Type d'astre</label>
<select name="type" id="field_type" onchange="updateIconPreview()">
<option value="empty">Vide / Espace</option>
<?php foreach ($object_types_db as $ot): ?>
<option value="<?php echo $ot['slug']; ?>"><?php echo htmlspecialchars($ot['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div id="object_details" style="display: none;">
<div class="form-group" style="background: rgba(136, 192, 208, 0.1); padding: 15px; border-radius: 4px; border: 1px solid #88c0d0;">
<label style="color: #88c0d0;">Statut Forcé (Laisse vide pour calcul automatique)</label>
<select name="manual_status" id="field_status">
<option value="">-- Calcul Automatique --</option>
<?php foreach ($statuses_db as $st): ?>
<option value="<?php echo $st['slug']; ?>"><?php echo htmlspecialchars($st['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label><i class="fa-solid fa-satellite-dish"></i> Contrôle Orbital (%)</label>
<div class="control-bars">
<?php foreach($factions_db as $f): ?>
<div class="control-bar-row">
<div class="control-bar-label">
<span class="faction-dot" style="background: <?php echo $f['color']; ?>;"></span>
<?php echo htmlspecialchars($f['name']); ?>
</div>
<input type="range" name="orbital_controls[<?php echo $f['id']; ?>]" class="control-bar-input orb-input" data-faction="<?php echo $f['id']; ?>" min="0" max="100" value="0" oninput="updateRangeVal(this)">
<div class="control-bar-value"><span class="val-display">0</span>%</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="form-group" style="border-top: 1px solid #334155; padding-top: 15px;">
<label style="display: flex; justify-content: space-between; align-items: center;">
<span><i class="fa-solid fa-city"></i> Établissements & Villes</span>
<button type="button" class="btn-add-settlement" onclick="addSettlement()" style="width: auto; margin-bottom: 0;">+ AJOUTER</button>
</label>
<div id="settlements_container" style="margin-top: 10px;"></div>
</div>
</div>
<button type="submit" class="btn-save">APPLIQUER LES CHANGEMENTS</button>
<button type="button" class="btn-cancel" onclick="closeModal()">ANNULER</button>
</form>
</div>
</div>
<script>
const factions = <?php echo json_encode($factions_db); ?>;
const settlementTypes = <?php echo json_encode($settlement_types_db); ?>;
function updateRangeVal(el) {
const display = el.nextElementSibling.querySelector('.val-display');
if (display) display.innerText = el.value;
handleCoupledSliders(el);
}
function handleCoupledSliders(el) {
const group = el.closest('.control-bars');
const inputs = Array.from(group.querySelectorAll('input[type="range"]'));
const otherInputs = inputs.filter(i => i !== el);
let newValue = parseInt(el.value);
let otherSumRequired = 100 - newValue;
let currentOtherSum = otherInputs.reduce((s, i) => s + parseInt(i.value), 0);
if (currentOtherSum > 0) {
let totalAdded = 0;
otherInputs.forEach((input) => {
let currentVal = parseInt(input.value);
let newVal = Math.floor((currentVal / currentOtherSum) * otherSumRequired);
input.value = newVal;
totalAdded += newVal;
});
// Adjustment for rounding
let diff = otherSumRequired - totalAdded;
if (diff !== 0) {
for (let i of otherInputs) {
let v = parseInt(i.value);
if (v + diff >= 0 && v + diff <= 100) {
i.value = v + diff;
break;
}
}
}
} else if (otherInputs.length > 0) {
// All others were 0, give all to the first one
otherInputs[0].value = otherSumRequired;
}
// Update all displays in the group
inputs.forEach(i => {
const disp = i.nextElementSibling.querySelector('.val-display');
if (disp) disp.innerText = i.value;
});
}
function editSlot(num, data) {
document.getElementById('slot_num').value = num;
document.getElementById('modalTitle').innerText = "Édition de la case " + num;
if (data) {
document.getElementById('slot_id').value = data.id;
document.getElementById('field_name').value = data.name;
document.getElementById('field_type').value = data.type;
document.getElementById('field_status').value = data.status === 'sta_auto' ? '' : data.status;
// Orbital controls
document.querySelectorAll('.orb-input').forEach(input => {
const fid = input.dataset.faction;
const val = data.orbital_controls && data.orbital_controls[fid] ? data.orbital_controls[fid] : 0;
input.value = val;
const display = input.nextElementSibling.querySelector('.val-display');
if (display) display.innerText = val;
});
// Settlements
const container = document.getElementById('settlements_container');
container.innerHTML = '';
if (data.cities) {
data.cities.forEach(city => addSettlement(city));
}
} else {
document.getElementById('slot_id').value = "";
document.getElementById('field_name').value = "Vide";
document.getElementById('field_type').value = "empty";
document.getElementById('field_status').value = "";
document.querySelectorAll('.orb-input').forEach(input => {
input.value = (input.dataset.faction == 1) ? 100 : 0; // Default to "Aucune" 100%
const display = input.nextElementSibling.querySelector('.val-display');
if (display) display.innerText = input.value;
});
document.getElementById('settlements_container').innerHTML = '';
}
updateIconPreview();
document.getElementById('editModal').style.display = 'flex';
}
function addSettlement(data = null) {
const container = document.getElementById('settlements_container');
const index = container.children.length;
const div = document.createElement('div');
div.className = 'settlement-item';
let typeOptions = '<option value="">Type d\'établissement...</option>';
settlementTypes.forEach(st => {
typeOptions += `<option value="${st.id}" ${data && data.settlement_type_id == st.id ? 'selected' : ''}>${st.name}</option>`;
});
let factionControls = '';
factions.forEach(f => {
let val = data && data.controls && data.controls[f.id] ? data.controls[f.id] : 0;
// If new settlement and faction is "Aucune", default to 100
if (!data && f.id == 1) val = 100;
factionControls += `
<div class="control-bar-row">
<div class="control-bar-label"><span class="faction-dot" style="background:${f.color}"></span> ${f.name}</div>
<input type="range" name="cities[${index}][controls][${f.id}]" class="control-bar-input" min="0" max="100" value="${val}" oninput="updateRangeVal(this)">
<div class="control-bar-value"><span class="val-display">${val}</span>%</div>
</div>
`;
});
div.innerHTML = `
<button type="button" class="btn-remove-settlement" onclick="this.parentElement.remove()">×</button>
<input type="hidden" name="cities[${index}][id]" value="${data ? data.id : ''}">
<div class="compact-row" style="margin-bottom:10px;"><div class="form-group" style="flex:2"><label>Nom de la ville</label><input type="text" name="cities[${index}][name]" value="${data ? data.name : ''}" required></div><div class="form-group" style="flex:1"><label>Type</label><select name="cities[${index}][type_id]">${typeOptions}</select></div></div>
<div class="control-bars">${factionControls}</div>
`;
container.appendChild(div);
}
function updateIconPreview() {
const type = document.getElementById('field_type').value;
document.getElementById('object_details').style.display = (type === 'empty') ? 'none' : 'block';
}
function closeModal() { document.getElementById('editModal').style.display = 'none'; }
function editSector() { document.getElementById('sectorModal').style.display = 'flex'; }
function closeSectorModal() { document.getElementById('sectorModal').style.display = 'none'; }
</script>
</body>
</html>