38676-vm/gm_console.php
Flatlogic Bot 2c4e1bc5b5 Alpha V0.9
2026-02-25 19:19:04 +00:00

807 lines
41 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';
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);
$object_types_map = []; foreach($object_types_db as $ot) { $object_types_map[$ot["slug"]] = $ot; }
$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;
}
}
// Derive Status and Faction from Settlements
$status = 'sta_inhabited';
$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);
if (count($active_factions) > 1) {
$status = 'sta_hostile';
} else {
if ($total_non_aucun >= ($num_cities * 100)) {
$status = 'sta_controlled';
} else {
$status = 'sta_contested';
}
}
} else if ($type !== 'empty') {
$status = 'sta_inhabited';
$faction_id = null;
}
// Manual status override
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'] = [];
}
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'];
}
}
foreach ($all_cities as $city) {
$city['controls'] = $city_controls[$city['id']] ?? [];
foreach ($grid as &$slot_data) {
if ($slot_data && $slot_data['id'] == $city['planet_id']) {
$slot_data['cities'][] = $city;
}
}
}
unset($slot_data);
}
$stmt = $db->prepare("SELECT name, status FROM sectors WHERE id = ?");
$stmt->execute([$sector_id]);
$sector_info = $stmt->fetch();
} else {
$stmt = $db->prepare("SELECT sector_id, slot, status, type FROM planets WHERE galaxy_id = ? ORDER BY sector_id, slot ASC");
$stmt->execute([$galaxy_id]);
$all_planets = $stmt->fetchAll();
$sector_data = [];
$active_sectors = [];
foreach ($all_planets as $p) {
$sector_data[$p['sector_id']][$p['slot']] = ['status' => $p['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)';
return $statuses_map[$status]['color'] ?? 'rgba(255,255,255,0.05)';
}
?>
<!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; }
.faction-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
</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" 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
$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-plus"></i></div>
<?php endif; ?>
</div>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<!-- Edit Slot Modal -->
<div id="editModal">
<div class="modal-content">
<h3 id="modalTitle">Éditer Case</h3>
<form method="POST">
<input type="hidden" name="action" value="update_slot">
<input type="hidden" name="slot_id" id="form_slot_id">
<input type="hidden" name="slot_num" id="form_slot_num">
<input type="hidden" name="galaxy_id" value="<?php echo $galaxy_id; ?>">
<input type="hidden" name="sector_id" value="<?php echo $sector_id; ?>">
<div style="display: flex; gap: 15px;">
<div class="form-group" style="flex: 2;">
<label>Nom de l'objet / Planète</label>
<input type="text" name="name" id="form_name" placeholder="Ex: Terra Nova...">
</div>
<div class="form-group" style="flex: 1;">
<label>Type d'objet</label>
<select name="type" id="form_type" onchange="updateControlToggles()">
<option value="empty">VIDE (Suppr)</option>
<?php foreach($object_types_db as $ot): ?>
<option value="<?php echo $ot['slug']; ?>"><?php echo $ot['name']; ?> (<?php echo $ot['slug']; ?>)</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-group">
<label>Statut (Automatique si non spécifié)</label>
<select name="manual_status" id="form_status">
<option value="">-- Automatique --</option>
<?php foreach($statuses_db as $s): ?>
<option value="<?php echo $s["slug"]; ?>"><?php echo $s["name"]; ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- Orbital Faction Control Section -->
<div id="orbitalSectionWrapper" style="background: rgba(0,0,0,0.1); padding: 15px; border-radius: 6px; border: 1px solid #334155; margin-bottom: 20px;">
<label style="font-size: 11px; color: #88c0d0; font-weight: bold; display: block; margin-bottom: 15px; text-align: center; border-bottom: 1px solid #334155; padding-bottom: 8px;">CONTRÔLE ORBITAL PAR FACTION (%)</label>
<div id="orbitalControlContainer" class="control-bars">
<!-- Orbital sliders injected by JS -->
</div>
</div>
<div id="terrestrialSectionWrapper" style="background: rgba(0,0,0,0.1); padding: 15px; border-radius: 6px; border: 1px solid #334155; margin-bottom: 20px;">
<label style="font-size: 11px; color: #88c0d0; font-weight: bold; display: block; margin-bottom: 15px; text-align: center; border-bottom: 1px solid #334155; padding-bottom: 8px;">ÉTABLISSEMENTS & PROGRESSIONS</label>
<div id="settlementsContainer">
<!-- Settlements will be injected here -->
</div>
<button type="button" class="btn-add-settlement" onclick="addSettlementRow()"><i class="fa-solid fa-plus"></i> AJOUTER UN ÉTABLISSEMENT</button>
</div>
<button type="submit" class="btn-save">ENREGISTRER</button>
<button type="button" class="btn-cancel" onclick="closeModal()">ANNULER</button>
</form>
</div>
</div>
<!-- Edit Sector Modal -->
<div id="sectorModal">
<div class="modal-content">
<h3>Paramètres du Secteur</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>Statut Global du Secteur</label>
<select name="sector_status">
<option value="unexplored" <?php echo ($sector_info['status'] ?? '') == 'unexplored' ? 'selected' : ''; ?>>Inexploré</option>
<option value="stable" <?php echo ($sector_info['status'] ?? '') == 'stable' ? 'selected' : ''; ?>>Stable / Pacifique</option>
<option value="hostile" <?php echo ($sector_info['status'] ?? '') == 'hostile' ? 'selected' : ''; ?>>Hostile / Contesté</option>
<option value="war" <?php echo ($sector_info['status'] ?? '') == 'war' ? 'selected' : ''; ?>>Zone de Guerre Mondiale</option>
</select>
</div>
<button type="submit" class="btn-save">METTRE À JOUR LE SECTEUR</button>
<button type="button" class="btn-cancel" onclick="closeSectorModal()">ANNULER</button>
</form>
</div>
</div>
<script>
let settlementIndex = 0;
const settlementTypes = <?php echo json_encode($settlement_types_db); ?>;
const typesMap = <?php echo json_encode($object_types_map); ?>;
const allFactions = <?php echo json_encode($factions_db); ?>;
const AUCUN_ID = 1; // ID for "Aucune" faction
function initOrbitalSliders(orbitalData = null) {
const container = document.getElementById('orbitalControlContainer');
container.innerHTML = '';
const hasExisting = orbitalData && Object.keys(orbitalData).length > 0;
allFactions.forEach(f => {
let val = 0;
if (hasExisting) {
val = orbitalData[f.id] !== undefined ? parseInt(orbitalData[f.id]) : 0;
} else {
val = (f.id == AUCUN_ID ? 100 : 0);
}
const div = document.createElement('div');
div.className = 'control-bar-row';
div.innerHTML = `
<div class="control-bar-label" title="${f.name}">
<span class="faction-dot" style="background: ${f.color || '#808080'}"></span>
${f.name}
</div>
<input type="range" name="orbital_controls[${f.id}]"
class="control-bar-input orbital-slider"
data-faction-id="${f.id}"
min="0" max="100" value="${val}"
oninput="handleSliderChangeGeneric('orbital-slider', ${f.id}, this.value)">
<div class="control-bar-value" id="val_orbital_${f.id}">${val}%</div>
`;
container.appendChild(div);
});
}
function addSettlementRow(data = null) {
const container = document.getElementById('settlementsContainer');
const index = settlementIndex++;
const div = document.createElement('div');
div.className = 'settlement-item';
div.id = 'settlement_row_' + index;
let html = `<button type="button" class="btn-remove-settlement" onclick="document.getElementById('settlement_row_${index}').remove()" title="Supprimer">×</button>`;
html += `<input type="hidden" name="cities[${index}][id]" value="${data ? data.id : 0}">`;
html += `<div class="compact-row">`;
html += `<div class="form-group" style="flex: 2;"><label>Nom</label>`;
html += `<input type="text" name="cities[${index}][name]" value="${data ? data.name : ''}" placeholder="Ex: New Hope"></div>`;
html += `<div class="form-group" style="flex: 1;"><label>Type</label><select name="cities[${index}][type_id]">`;
settlementTypes.forEach(t => {
const sel = (data && data.settlement_type_id == t.id) ? 'selected' : '';
html += `<option value="${t.id}" ${sel}>${t.name}</option>`;
});
html += `</select></div></div>`;
// Progress bars for each faction
html += `<div class="control-bars"><label style="font-size: 10px; color: #8c92a3; font-weight: bold; margin-bottom: 5px;">CONTRÔLE PAR FACTION (%)</label>`;
const initialControls = {};
const hasAnyControls = data && data.controls && Object.keys(data.controls).length > 0;
allFactions.forEach(f => {
if (hasAnyControls) {
initialControls[f.id] = (data.controls[f.id] !== undefined) ? parseInt(data.controls[f.id]) : 0;
} else {
initialControls[f.id] = (f.id == AUCUN_ID ? 100 : 0);
}
});
allFactions.forEach(f => {
const controlVal = initialControls[f.id];
html += `
<div class="control-bar-row">
<div class="control-bar-label" title="${f.name}">
<span class="faction-dot" style="background: ${f.color || '#808080'}"></span>
${f.name}
</div>
<input type="range" name="cities[${index}][controls][${f.id}]"
class="control-bar-input city-slider-${index}"
data-faction-id="${f.id}"
min="0" max="100" value="${controlVal}"
oninput="handleSliderChangeGeneric('city-slider-${index}', ${f.id}, this.value, ${index})">
<div class="control-bar-value" id="val_${index}_${f.id}">${controlVal}%</div>
</div>`;
});
html += `</div>`;
div.innerHTML = html;
container.appendChild(div);
}
function handleSliderChangeGeneric(className, changedFid, newVal, rowIdx = null) {
newVal = parseInt(newVal);
const sliders = Array.from(document.querySelectorAll(`.${className}`));
let otherSum = 0;
sliders.forEach(s => {
if (parseInt(s.dataset.factionId) !== changedFid) {
otherSum += parseInt(s.value);
}
});
let targetOtherSum = 100 - newVal;
if (otherSum === 0 && targetOtherSum > 0) {
const aucun = sliders.find(s => parseInt(s.dataset.factionId) === AUCUN_ID);
if (aucun && changedFid !== AUCUN_ID) {
aucun.value = targetOtherSum;
} else {
const other = sliders.find(s => parseInt(s.dataset.factionId) !== AUCUN_ID);
if (other) other.value = targetOtherSum;
}
} else {
let diff = targetOtherSum - otherSum;
if (diff < 0) {
const aucun = sliders.find(s => parseInt(s.dataset.factionId) === AUCUN_ID);
if (aucun && parseInt(aucun.dataset.factionId) !== changedFid && parseInt(aucun.value) > 0) {
let take = Math.min(parseInt(aucun.value), Math.abs(diff));
aucun.value = parseInt(aucun.value) - take;
diff += take;
}
if (diff < 0) {
sliders.forEach(s => {
if (diff === 0 || parseInt(s.dataset.factionId) === changedFid || parseInt(s.dataset.factionId) === AUCUN_ID) return;
let val = parseInt(s.value);
let take = Math.min(val, Math.abs(diff));
s.value = val - take;
diff += take;
});
}
} else if (diff > 0) {
const aucun = sliders.find(s => parseInt(s.dataset.factionId) === AUCUN_ID);
if (aucun && parseInt(aucun.dataset.factionId) !== changedFid) {
aucun.value = parseInt(aucun.value) + diff;
diff = 0;
} else {
const other = sliders.find(s => parseInt(s.dataset.factionId) !== changedFid);
if (other) {
other.value = parseInt(other.value) + diff;
diff = 0;
}
}
}
}
sliders.forEach(s => {
const displayId = rowIdx !== null ? `val_${rowIdx}_${s.dataset.factionId}` : `val_orbital_${s.dataset.factionId}`;
document.getElementById(displayId).innerText = s.value + '%';
});
}
function updateControlToggles() {
const type = document.getElementById('form_type').value;
const typeInfo = typesMap[type] || { orbital_control_enabled: 0, terrestrial_control_enabled: 0 };
const orbWrapper = document.getElementById('orbitalSectionWrapper');
const terrWrapper = document.getElementById('terrestrialSectionWrapper');
orbWrapper.style.display = (typeInfo.orbital_control_enabled == 1) ? 'block' : 'none';
terrWrapper.style.display = (typeInfo.terrestrial_control_enabled == 1) ? 'block' : 'none';
// Disable inputs in hidden sections to prevent them from being submitted
orbWrapper.querySelectorAll('input, select, textarea').forEach(el => el.disabled = (typeInfo.orbital_control_enabled != 1));
terrWrapper.querySelectorAll('input, select, textarea, button:not(.btn-cancel):not(.btn-save)').forEach(el => el.disabled = (typeInfo.terrestrial_control_enabled != 1));
}
function editSlot(num, data) {
document.getElementById('form_slot_num').value = num;
document.getElementById('form_slot_id').value = data ? data.id : 0;
document.getElementById('modalTitle').innerText = 'Éditer Case #' + num;
if (data) {
document.getElementById('form_name').value = data.name;
document.getElementById('form_type').value = data.type;
document.getElementById('form_status').value = data.status;
// Load orbital sliders
initOrbitalSliders(data.orbital_controls);
// Load settlements
document.getElementById('settlementsContainer').innerHTML = '';
settlementIndex = 0;
if (data.cities && data.cities.length > 0) {
data.cities.forEach(c => addSettlementRow(c));
}
} else {
document.getElementById('form_name').value = '';
document.getElementById('form_type').value = 'empty';
document.getElementById('form_status').value = '';
initOrbitalSliders(null);
document.getElementById('settlementsContainer').innerHTML = '';
}
updateControlToggles();
document.getElementById('editModal').style.display = 'flex';
}
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>