1388 lines
59 KiB
C#

// AgrandissementSalle.cs - v7.0 Multi-compatible
//
// v7.0 : Support multijoueur via SynchronisationAgrandissement
// - Nouvelle methode DemanderAgrandir() : appelee par PlayerInteraction, route
// vers le singleton SynchronisationAgrandissement qui orchestre la sync
// - Nouvelle methode publique AgrandirLocal(bool instantane) : execute l'agrandissement
// sur l'instance locale (serveur OU client). Parametre instantane=true pour le rejeu
// des clients qui rejoignent en cours de partie (pas d'animation, pas d'explosion)
// - Methode Agrandir() originale conservee pour compat editor/test
// - Cote dedicated server : skip debris/anim visuelle (pas de gestion de shader/material)
// mais execute bien la construction logique pour que les emplacements existent
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class AgrandissementSalle : MonoBehaviour
{
public enum DirectionAgrandissement { Nord, Sud, Est, Ouest }
[Header("Agrandissement")]
public DirectionAgrandissement direction = DirectionAgrandissement.Nord;
public float nouvelleLargeur = 10f;
public float nouvelleProfondeur = 10f;
[Header("Prix")]
public float prix = 25000f;
[Header("Animation")]
public int nombreDebris = 25;
public float forceExplosion = 5f;
public float dureeDebris = 3f;
public float dureeConstruction = 4f;
[Header("Panneau")]
public float largeurPanneau = 0.6f;
public float hauteurPanneau = 0.4f;
public Vector3 offsetPanneau = new Vector3(0f, 0f, 0.1f);
public Color couleurPanneau = new Color(0.1f, 0.3f, 0.6f, 1f);
[Header("=== Ajustements manuels ===")]
[Tooltip("Décalage manuel de la grille de dalles en X/Z (local à l'extension)")]
public Vector2 offsetGrille = Vector2.zero;
[Tooltip("Recul des murs latéraux côté passage (évite chevauchement avec la salle existante)")]
public float margeReculMurs = 0.0f;
[Tooltip("Largeur de l'ouverture entre salle et extension (0 = toute la largeur du mur cassé)")]
public float largeurOuverture = 0f;
[Header("=== Debug Gizmos ===")]
[Tooltip("Afficher les gizmos de debug dans la Scene view")]
public bool afficherGizmos = true;
public Color couleurGizmoZone = new Color(0f, 1f, 0.5f, 0.3f);
public Color couleurGizmoGrille = new Color(1f, 1f, 0f, 0.5f);
[Header("État")]
public bool estAgrandi = false;
public bool enAnimation = false;
private GameObject _panneau;
private SalleDatacenter _salleRef;
// ==================== PANNEAU ====================
public void CreerPanneau()
{
if (_panneau != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying) DestroyImmediate(_panneau);
else Destroy(_panneau);
#else
Destroy(_panneau);
#endif
}
_panneau = new GameObject("Panneau_Agrandissement");
// On parente au mur (transform) pour que l'Interactable fonctionne,
// mais on compense le scale non-uniforme du mur
_panneau.transform.SetParent(transform);
_panneau.transform.localPosition = Vector3.zero;
_panneau.transform.localRotation = Quaternion.identity;
_panneau.transform.localScale = Vector3.one;
// Calculer la position WORLD du panneau (centre du mur, décalé vers l'intérieur)
SalleDatacenter salle = FindObjectOfType<SalleDatacenter>();
float sL = salle != null ? salle.longueur : 15f;
float sW = salle != null ? salle.largeur : 10f;
float hMur = salle != null ? salle.hauteurMurs / 2f : 1.6f;
Vector3 salleOrigin = salle != null ? salle.transform.position : Vector3.zero;
Vector3 panneauWorldPos = Vector3.zero;
Quaternion panneauWorldRot = Quaternion.identity;
switch (direction)
{
case DirectionAgrandissement.Nord:
panneauWorldPos = salleOrigin + new Vector3(sL / 2f, hMur, sW - 0.1f);
panneauWorldRot = Quaternion.Euler(0, 180, 0);
break;
case DirectionAgrandissement.Sud:
panneauWorldPos = salleOrigin + new Vector3(sL / 2f, hMur, 0.1f);
panneauWorldRot = Quaternion.Euler(0, 0, 0);
break;
case DirectionAgrandissement.Est:
panneauWorldPos = salleOrigin + new Vector3(sL - 0.1f, hMur, sW / 2f);
panneauWorldRot = Quaternion.Euler(0, -90, 0);
break;
case DirectionAgrandissement.Ouest:
panneauWorldPos = salleOrigin + new Vector3(0.1f, hMur, sW / 2f);
panneauWorldRot = Quaternion.Euler(0, 90, 0);
break;
}
// Détacher temporairement du mur pour positionner en world-space sans déformation
_panneau.transform.SetParent(null);
_panneau.transform.position = panneauWorldPos;
_panneau.transform.rotation = panneauWorldRot;
_panneau.transform.localScale = Vector3.one;
// Reparenter au mur (nécessaire pour l'Interactable et Agrandir)
// worldPositionStays = true pour garder la position/rotation world
_panneau.transform.SetParent(transform, true);
GameObject fond = GameObject.CreatePrimitive(PrimitiveType.Cube);
fond.name = "Panneau_Fond";
fond.transform.SetParent(_panneau.transform);
fond.transform.localPosition = Vector3.zero;
fond.transform.localScale = new Vector3(largeurPanneau, hauteurPanneau, 0.02f);
if (!DedicatedServerMode.IsDedicatedServer)
{
Material matFond = new Material(Shader.Find("Standard"));
matFond.color = couleurPanneau;
matFond.EnableKeyword("_EMISSION");
matFond.SetColor("_EmissionColor", couleurPanneau * 0.3f);
fond.GetComponent<Renderer>().sharedMaterial = matFond;
}
GameObject bordure = GameObject.CreatePrimitive(PrimitiveType.Cube);
bordure.name = "Panneau_Bordure";
bordure.transform.SetParent(_panneau.transform);
bordure.transform.localPosition = new Vector3(0, 0, -0.005f);
bordure.transform.localScale = new Vector3(largeurPanneau + 0.03f, hauteurPanneau + 0.03f, 0.01f);
if (!DedicatedServerMode.IsDedicatedServer)
{
Material matBord = new Material(Shader.Find("Standard"));
matBord.color = new Color(0.15f, 0.5f, 0.8f);
matBord.EnableKeyword("_EMISSION");
matBord.SetColor("_EmissionColor", new Color(0.1f, 0.4f, 0.7f) * 1.5f);
bordure.GetComponent<Renderer>().sharedMaterial = matBord;
}
Collider bc = bordure.GetComponent<Collider>();
if (bc != null) DestroyImmediate(bc);
// Textes sur la face AVANT du panneau (côté joueur = +Z local)
if (!DedicatedServerMode.IsDedicatedServer)
{
CreerTexte(_panneau.transform, "AGRANDIR", new Vector3(0, 0.1f, 0.015f), 200, 0.002f, new Color(0.8f, 0.9f, 1f));
CreerTexte(_panneau.transform, nouvelleLargeur + "m x " + nouvelleProfondeur + "m", new Vector3(0, 0.02f, 0.015f), 160, 0.0015f, new Color(0.6f, 0.8f, 0.9f));
CreerTexte(_panneau.transform, prix.ToString("N0") + " EUR", new Vector3(0, -0.08f, 0.015f), 180, 0.0018f, new Color(1f, 0.85f, 0.2f));
}
Interactable inter = fond.AddComponent<Interactable>();
inter.nomEquipement = "Agrandissement " + nouvelleLargeur + "x" + nouvelleProfondeur + "m";
inter.tailleU = 0;
}
// ==================== AGRANDIR ====================
/// <summary>
/// v7.0 : Point d'entree multijoueur. Appele par PlayerInteraction quand le joueur
/// clique [E] sur un panneau d'agrandissement. Route vers le singleton qui orchestre
/// la sync via Cmd/Rpc. En solo, le singleton appelle directement AgrandirLocal().
/// </summary>
public void DemanderAgrandir()
{
if (estAgrandi || enAnimation) return;
// Validation economique locale (pre-check, affinable cote serveur plus tard)
// En sandbox, TenterAchat retourne toujours true.
if (GameEconomy.Instance != null && !GameEconomy.Instance.modeSandbox)
{
if (!GameEconomy.Instance.TenterAchat(prix))
{
Debug.Log("[AgrandissementSalle] Fonds insuffisants pour agrandir");
return;
}
}
else if (GameEconomy.Instance != null)
{
GameEconomy.Instance.TenterAchat(prix); // log + compteur sandbox
}
// Recuperer le salleId. Si pas encore enregistree, on utilise 1 par defaut
// (cas mono-salle au demarrage).
int salleId = 1;
SalleDatacenter salleParente = FindObjectOfType<SalleDatacenter>();
if (salleParente != null && salleParente.salleId > 0)
salleId = salleParente.salleId;
if (SynchronisationAgrandissement.Instance != null)
{
SynchronisationAgrandissement.Instance.DemanderAgrandissement(salleId, direction.ToString());
}
else
{
// Fallback : si le singleton n'existe pas (scene sans multi), on execute en local direct
Debug.LogWarning("[AgrandissementSalle] SynchronisationAgrandissement.Instance introuvable, fallback solo");
AgrandirLocal(false);
}
}
/// <summary>
/// Version originale conservee pour compat (editor, boutons de test).
/// Equivalent a AgrandirLocal(false).
/// </summary>
public void Agrandir()
{
AgrandirLocal(false);
}
/// <summary>
/// v7.0 : Execute l'agrandissement sur l'instance LOCALE (pas de reseau).
/// Appele par SynchronisationAgrandissement.Rpc ou par Agrandir() en mode solo.
///
/// instantane=true : pas d'animation, pas d'explosion, construction immediate.
/// Utilise pour le rejeu cote client qui rejoint une partie en cours.
/// </summary>
public void AgrandirLocal(bool instantane)
{
if (estAgrandi || enAnimation) return;
_salleRef = FindObjectOfType<SalleDatacenter>();
if (_salleRef == null) { Debug.LogError("SalleDatacenter introuvable !"); return; }
// Créer un runner persistant pour toute l'animation
// (le gameObject du mur peut être détruit/désactivé par d'autres scripts)
GameObject runnerObj = new GameObject("AgrandissementRunner_" + direction);
CoroutineRunner runner = runnerObj.AddComponent<CoroutineRunner>();
runner.StartCoroutine(AnimationAgrandissement(runner, instantane));
}
IEnumerator AnimationAgrandissement(CoroutineRunner runner, bool instantane)
{
enAnimation = true;
Renderer murRend = GetComponent<Renderer>();
Collider murCol = GetComponent<Collider>();
if (murRend != null) murRend.enabled = false;
if (murCol != null) murCol.enabled = false;
if (_panneau != null) _panneau.SetActive(false);
// === PHASE 1 : EXPLOSION ===
// Skip cote serveur (pas de shader) et en mode instantane
bool skipVisuel = instantane || DedicatedServerMode.IsDedicatedServer;
if (!skipVisuel)
{
Material matDebris = new Material(Shader.Find("Standard"));
matDebris.color = _salleRef.couleurMur;
Vector3 dirVec = GetDirectionVector();
List<GameObject> debris = new List<GameObject>();
for (int i = 0; i < nombreDebris; i++)
{
GameObject d = GameObject.CreatePrimitive(PrimitiveType.Cube);
d.name = "Debris_" + i;
float rx = Random.Range(-transform.localScale.x / 2f, transform.localScale.x / 2f);
float ry = Random.Range(0f, _salleRef.hauteurMurs);
d.transform.position = transform.position + transform.right * rx + Vector3.up * ry + Random.insideUnitSphere * 0.1f;
float sz = Random.Range(0.08f, 0.25f);
d.transform.localScale = new Vector3(sz, sz * Random.Range(0.5f, 1.5f), sz * Random.Range(0.3f, 0.8f));
d.transform.rotation = Random.rotation;
d.GetComponent<Renderer>().material = matDebris;
Rigidbody rb = d.AddComponent<Rigidbody>();
rb.mass = Random.Range(0.5f, 2f);
rb.AddForce((dirVec + Vector3.up * Random.Range(0.2f, 0.6f) + Random.insideUnitSphere * 0.3f) * forceExplosion, ForceMode.Impulse);
rb.AddTorque(Random.insideUnitSphere * 3f, ForceMode.Impulse);
debris.Add(d);
}
yield return new WaitForSeconds(2f);
foreach (var d in debris) if (d != null) Destroy(d);
}
// === PHASE 1.5 : NETTOYER LA SALLE EXISTANTE ===
NettoyerSalleExistante();
// === PHASE 2 : CALCUL POSITION ===
Vector3 salleOrigineMonde = _salleRef.transform.position;
Vector3 origineNouvelle = CalculerOrigineNouvelleZone(salleOrigineMonde);
float zoneL, zoneW;
if (direction == DirectionAgrandissement.Nord || direction == DirectionAgrandissement.Sud)
{ zoneL = nouvelleLargeur; zoneW = nouvelleProfondeur; }
else
{ zoneL = nouvelleProfondeur; zoneW = nouvelleLargeur; }
GameObject ext = new GameObject("Salle_Extension_" + direction);
ext.transform.position = origineNouvelle;
// === PHASE 3 : SOL ===
Debug.Log("AgrandissementSalle: PHASE 3 - Début construction sol");
yield return runner.StartCoroutine(ConstruireSol(ext.transform, zoneL, zoneW, origineNouvelle, salleOrigineMonde, skipVisuel));
Debug.Log("AgrandissementSalle: PHASE 3 - Sol terminé");
if (!skipVisuel) yield return new WaitForSeconds(0.2f);
// === PHASE 4 : MURS ===
Debug.Log("AgrandissementSalle: PHASE 4 - Début construction murs");
yield return runner.StartCoroutine(ConstruireMurs(ext.transform, zoneL, zoneW, skipVisuel));
Debug.Log("AgrandissementSalle: PHASE 4 - Murs terminés");
if (!skipVisuel) yield return new WaitForSeconds(0.2f);
// === PHASE 5 : PLAFOND ===
Debug.Log("AgrandissementSalle: PHASE 5 - Début construction plafond");
yield return runner.StartCoroutine(ConstruirePlafond(ext.transform, zoneL, zoneW, origineNouvelle, salleOrigineMonde, skipVisuel));
Debug.Log("AgrandissementSalle: PHASE 5 - Plafond terminé");
if (!skipVisuel) yield return new WaitForSeconds(0.1f);
// === PHASE 6 : ÉCLAIRAGE ===
// Skip cote serveur (lights inutiles)
if (!DedicatedServerMode.IsDedicatedServer)
{
Debug.Log("AgrandissementSalle: PHASE 6 - Début éclairage");
yield return runner.StartCoroutine(ConstruireEclairage(ext.transform, zoneL, zoneW, origineNouvelle, salleOrigineMonde, skipVisuel));
Debug.Log("AgrandissementSalle: PHASE 6 - Éclairage terminé");
}
// === PHASE 7 : EMPLACEMENTS DE BAIES ===
Debug.Log("AgrandissementSalle: PHASE 7 - Génération emplacements baies");
GenererEmplacementsExtension(ext.transform, zoneL, zoneW);
Debug.Log("AgrandissementSalle: PHASE 7 - Construction complète !");
// Nettoyage
try
{
if (this != null && gameObject != null)
{
estAgrandi = true;
enAnimation = false;
Destroy(gameObject);
}
}
catch (System.Exception) { /* mur déjà détruit, pas grave */ }
Destroy(runner.gameObject);
}
// ==================== POSITION ====================
Vector3 CalculerOrigineNouvelleZone(Vector3 salleOrigine)
{
float sL = _salleRef.longueur;
float sW = _salleRef.largeur;
return CalculerOrigineNouvelleZoneEditor(salleOrigine, sL, sW);
}
public Vector3 CalculerOrigineNouvelleZoneEditor(Vector3 salleOrigine, float sL, float sW)
{
switch (direction)
{
case DirectionAgrandissement.Ouest:
return new Vector3(salleOrigine.x - nouvelleProfondeur, salleOrigine.y,
salleOrigine.z + sW / 2f - nouvelleLargeur / 2f);
case DirectionAgrandissement.Est:
return new Vector3(salleOrigine.x + sL, salleOrigine.y,
salleOrigine.z + sW / 2f - nouvelleLargeur / 2f);
case DirectionAgrandissement.Sud:
return new Vector3(salleOrigine.x + sL / 2f - nouvelleLargeur / 2f, salleOrigine.y,
salleOrigine.z - nouvelleProfondeur);
case DirectionAgrandissement.Nord:
return new Vector3(salleOrigine.x + sL / 2f - nouvelleLargeur / 2f, salleOrigine.y,
salleOrigine.z + sW);
default: return salleOrigine;
}
}
// ==================== GRILLE CONTINUE ====================
struct GrilleInfo
{
public float startLocalX, startLocalZ;
public int gridOffsetX, gridOffsetZ;
public int nbX, nbZ;
}
GrilleInfo CalculerGrilleContinue(Vector3 origineExt, Vector3 origineSalle,
float pas, float tailleDalle, float zoneL, float zoneW)
{
GrilleInfo g = new GrilleInfo();
float deltaX = origineExt.x - origineSalle.x - offsetGrille.x;
float deltaZ = origineExt.z - origineSalle.z - offsetGrille.y;
float demiDalle = tailleDalle / 2f;
// Premier indice global dont le BORD DROIT (centre + demiDalle) est > 0
// => la dalle est au moins partiellement visible dans la zone
g.gridOffsetX = Mathf.CeilToInt((deltaX - demiDalle) / pas);
g.gridOffsetZ = Mathf.CeilToInt((deltaZ - demiDalle) / pas);
g.startLocalX = g.gridOffsetX * pas + demiDalle - deltaX;
g.startLocalZ = g.gridOffsetZ * pas + demiDalle - deltaZ;
// Compter les dalles dont le BORD GAUCHE (centre - demiDalle) est < zoneL
// => la dalle est au moins partiellement dans la zone
g.nbX = 0;
float px = g.startLocalX;
while ((px - demiDalle) < zoneL - 0.001f) { g.nbX++; px += pas; }
g.nbZ = 0;
float pz = g.startLocalZ;
while ((pz - demiDalle) < zoneW - 0.001f) { g.nbZ++; pz += pas; }
return g;
}
// ==================== NETTOYAGE SALLE EXISTANTE ====================
void NettoyerSalleExistante()
{
if (_salleRef == null) return;
Transform salleT = _salleRef.transform;
// 1) Supprimer le mur correspondant à la direction
string nomMur = "";
switch (direction)
{
case DirectionAgrandissement.Nord: nomMur = "DC_MurNord"; break;
case DirectionAgrandissement.Sud: nomMur = "DC_MurSud"; break;
case DirectionAgrandissement.Est: nomMur = "DC_MurEst"; break;
case DirectionAgrandissement.Ouest: nomMur = "DC_MurOuest"; break;
}
// Chercher tous les objets à modifier/supprimer
Transform murT = null;
Transform plafondSolideT = null;
Transform solColliderT = null;
Transform sousSolT = null;
foreach (Transform child in salleT.GetComponentsInChildren<Transform>())
{
if (child.name == nomMur) murT = child;
else if (child.name == "DC_PlafondSolide") plafondSolideT = child;
else if (child.name == "DC_SolCollider") solColliderT = child;
else if (child.name == "DC_SousSol") sousSolT = child;
}
// Supprimer le mur
if (murT != null)
{
Debug.Log("AgrandissementSalle: Suppression " + nomMur);
Destroy(murT.gameObject);
}
// 2) Redimensionner plafond solide, sol collider et sous-sol
// pour qu'ils ne dépassent pas côté extension
// (on les réduit à la taille intérieure de la salle, sans débordement)
float sL = _salleRef.longueur;
float sW = _salleRef.largeur;
if (plafondSolideT != null)
{
// Réduire à longueur x largeur (sans + epaisseurMur * 2)
Vector3 sc = plafondSolideT.localScale;
plafondSolideT.localScale = new Vector3(sL, sc.y, sW);
plafondSolideT.localPosition = new Vector3(sL / 2f, plafondSolideT.localPosition.y, sW / 2f);
Debug.Log("AgrandissementSalle: PlafondSolide redimensionné à " + sL + "x" + sW);
}
if (solColliderT != null)
{
BoxCollider bc = solColliderT.GetComponent<BoxCollider>();
if (bc != null)
{
float solSommetN = _salleRef.hauteurSol - 0.01f;
float solBaseN = -0.1f;
float solEpN = solSommetN - solBaseN;
bc.size = new Vector3(sL, solEpN, sW);
}
float sCentreN = (_salleRef.hauteurSol - 0.01f + (-0.1f)) / 2f;
solColliderT.localPosition = new Vector3(sL / 2f, sCentreN, sW / 2f);
Debug.Log("AgrandissementSalle: SolCollider redimensionné");
}
if (sousSolT != null)
{
sousSolT.localScale = new Vector3(sL, sousSolT.localScale.y, sW);
sousSolT.localPosition = new Vector3(sL / 2f, sousSolT.localPosition.y, sW / 2f);
}
}
// ==================== SOL ====================
IEnumerator ConstruireSol(Transform parent, float longueur, float largeur,
Vector3 origineExt, Vector3 origineSalle, bool skipVisuel)
{
float hauteurSol = _salleRef.hauteurSol;
float tailleDalle = _salleRef.tailleDalle;
float epaisseurDalle = _salleRef.epaisseurDalle;
float espacement = _salleRef.espacementDalles;
float pas = tailleDalle + espacement;
Material matDalle = CreerMat(_salleRef.couleurDalleSol, 0.1f, 0.3f);
Material matVent = CreerMat(_salleRef.couleurDalleVentilee, 0.2f, 0.5f);
Material matDalleAlt = CreerMat(
new Color(_salleRef.couleurDalleSol.r - 0.03f, _salleRef.couleurDalleSol.g - 0.03f,
_salleRef.couleurDalleSol.b - 0.02f, 1f), 0.1f, 0.3f);
Material matPied = CreerMat(_salleRef.couleurPiedestal, 0.3f, 0.4f);
Material matSousSol = CreerMat(_salleRef.couleurSousSol, 0f, 0.1f);
// Sous-sol visuel
GameObject ss = GameObject.CreatePrimitive(PrimitiveType.Cube);
ss.name = "Ext_SousSol";
ss.transform.SetParent(parent);
ss.transform.localPosition = new Vector3(longueur / 2f, -0.05f, largeur / 2f);
ss.transform.localScale = new Vector3(longueur, 0.1f, largeur);
if (ss.GetComponent<Renderer>() != null && matSousSol != null)
ss.GetComponent<Renderer>().sharedMaterial = matSousSol;
ss.isStatic = true;
// Supprimer le collider du sous-sol visuel (le sol collider gère tout)
Collider ssCol = ss.GetComponent<Collider>();
if (ssCol != null) Destroy(ssCol);
// Collider sol - TOUJOURS cree (meme cote serveur pour le CharacterController)
GameObject solC = new GameObject("Ext_SolCollider");
solC.transform.SetParent(parent);
float solSommet = hauteurSol - 0.01f;
float solBase = -0.1f;
float solEp = solSommet - solBase;
float solCentre = (solSommet + solBase) / 2f;
solC.transform.localPosition = new Vector3(longueur / 2f, solCentre, largeur / 2f);
BoxCollider col = solC.AddComponent<BoxCollider>();
col.size = new Vector3(longueur, solEp, largeur);
int layerSol = LayerMask.NameToLayer("Sol");
if (layerSol >= 0) solC.layer = layerSol;
// Dalle de fond continue
GameObject fondSol = GameObject.CreatePrimitive(PrimitiveType.Cube);
fondSol.name = "Ext_FondSol";
fondSol.transform.SetParent(parent);
fondSol.transform.localPosition = new Vector3(longueur / 2f, hauteurSol - 0.001f, largeur / 2f);
fondSol.transform.localScale = new Vector3(longueur, epaisseurDalle, largeur);
if (fondSol.GetComponent<Renderer>() != null && matDalle != null)
fondSol.GetComponent<Renderer>().sharedMaterial = matDalle;
fondSol.isStatic = true;
Collider fc = fondSol.GetComponent<Collider>();
if (fc != null) Destroy(fc);
// === Dalles alignées grille globale ===
GrilleInfo g = CalculerGrilleContinue(origineExt, origineSalle, pas, tailleDalle, longueur, largeur);
Debug.Log($"Extension Sol: {g.nbX}x{g.nbZ} dalles, start=({g.startLocalX:F3},{g.startLocalZ:F3})");
// Calculer les allées froides de l'extension
List<Vector2> alleesFroides = CalculerZonesAlleesFroidesExtension(longueur, largeur);
int count = 0;
int total = g.nbX * g.nbZ;
int dallesParFrame = Mathf.Max(1, total / Mathf.Max(1, (int)(dureeConstruction * 20)));
for (int ix = 0; ix < g.nbX; ix++)
{
for (int iz = 0; iz < g.nbZ; iz++)
{
float px = g.startLocalX + ix * pas;
float pz = g.startLocalZ + iz * pas;
int globalX = g.gridOffsetX + ix;
int globalZ = g.gridOffsetZ + iz;
// Vérifier si dans une allée froide
bool dansAlleeFroide = false;
foreach (Vector2 zone in alleesFroides)
{
if (pz >= zone.x && pz <= zone.y)
{
dansAlleeFroide = true;
break;
}
}
Material matChoisi;
if (dansAlleeFroide)
matChoisi = matVent;
else
matChoisi = ((Mathf.Abs(globalX + globalZ)) % 2 == 0) ? matDalle : matDalleAlt;
GameObject dalle = GameObject.CreatePrimitive(PrimitiveType.Cube);
dalle.name = $"Ext_Dalle_{ix}_{iz}";
dalle.transform.SetParent(parent);
dalle.transform.localPosition = new Vector3(px, hauteurSol, pz);
dalle.transform.localScale = new Vector3(tailleDalle, epaisseurDalle, tailleDalle);
if (dalle.GetComponent<Renderer>() != null && matChoisi != null)
dalle.GetComponent<Renderer>().sharedMaterial = matChoisi;
dalle.isStatic = true;
Collider dc = dalle.GetComponent<Collider>();
if (dc != null) Destroy(dc);
if (Mathf.Abs(globalX) % 2 == 0 && Mathf.Abs(globalZ) % 2 == 0)
{
float piedH = hauteurSol - epaisseurDalle / 2f;
GameObject pied = GameObject.CreatePrimitive(PrimitiveType.Cube);
pied.name = $"Ext_Pied_{ix}_{iz}";
pied.transform.SetParent(parent);
pied.transform.localPosition = new Vector3(px, piedH / 2f, pz);
pied.transform.localScale = new Vector3(0.04f, piedH, 0.04f);
if (pied.GetComponent<Renderer>() != null && matPied != null)
pied.GetComponent<Renderer>().sharedMaterial = matPied;
pied.isStatic = true;
Collider pc = pied.GetComponent<Collider>();
if (pc != null) Destroy(pc);
}
count++;
// Mode instantane : pas de yield, tout dans la meme frame
if (!skipVisuel && count % dallesParFrame == 0) yield return null;
}
}
}
// ==================== MURS ====================
IEnumerator ConstruireMurs(Transform parent, float longueur, float largeur, bool skipVisuel)
{
Material matMur = CreerMat(_salleRef.couleurMur, 0f, 0.2f);
float hauteur = _salleRef.hauteurMurs;
float ep = _salleRef.epaisseurMur;
float mY = hauteur / 2f;
// Recul automatique : les murs latéraux de l'extension doivent s'arrêter
// avant les murs existants de la salle (épaisseur du mur existant)
float recul = margeReculMurs + ep;
List<Vector3> positions = new List<Vector3>();
List<Vector3> tailles = new List<Vector3>();
switch (direction)
{
case DirectionAgrandissement.Ouest:
{
positions.Add(new Vector3(-ep / 2f, mY, largeur / 2f));
tailles.Add(new Vector3(ep, hauteur, largeur + ep * 2));
float murLen = longueur - recul;
positions.Add(new Vector3(murLen / 2f, mY, largeur + ep / 2f));
tailles.Add(new Vector3(murLen, hauteur, ep));
positions.Add(new Vector3(murLen / 2f, mY, -ep / 2f));
tailles.Add(new Vector3(murLen, hauteur, ep));
break;
}
case DirectionAgrandissement.Est:
{
positions.Add(new Vector3(longueur + ep / 2f, mY, largeur / 2f));
tailles.Add(new Vector3(ep, hauteur, largeur + ep * 2));
float murLen = longueur - recul;
float offX = recul + murLen / 2f;
positions.Add(new Vector3(offX, mY, largeur + ep / 2f));
tailles.Add(new Vector3(murLen, hauteur, ep));
positions.Add(new Vector3(offX, mY, -ep / 2f));
tailles.Add(new Vector3(murLen, hauteur, ep));
break;
}
case DirectionAgrandissement.Nord:
{
positions.Add(new Vector3(longueur / 2f, mY, largeur + ep / 2f));
tailles.Add(new Vector3(longueur + ep * 2, hauteur, ep));
float murLen = largeur - recul;
float offZ = recul + murLen / 2f;
positions.Add(new Vector3(longueur + ep / 2f, mY, offZ));
tailles.Add(new Vector3(ep, hauteur, murLen));
positions.Add(new Vector3(-ep / 2f, mY, offZ));
tailles.Add(new Vector3(ep, hauteur, murLen));
break;
}
case DirectionAgrandissement.Sud:
{
positions.Add(new Vector3(longueur / 2f, mY, -ep / 2f));
tailles.Add(new Vector3(longueur + ep * 2, hauteur, ep));
float murLen = largeur - recul;
positions.Add(new Vector3(longueur + ep / 2f, mY, murLen / 2f));
tailles.Add(new Vector3(ep, hauteur, murLen));
positions.Add(new Vector3(-ep / 2f, mY, murLen / 2f));
tailles.Add(new Vector3(ep, hauteur, murLen));
break;
}
}
for (int i = 0; i < positions.Count; i++)
{
GameObject mur = GameObject.CreatePrimitive(PrimitiveType.Cube);
mur.name = "Ext_Mur_" + i;
mur.transform.SetParent(parent);
if (skipVisuel)
{
// Construction immediate (rejeu ou serveur) : taille finale direct
mur.transform.localScale = tailles[i];
mur.transform.localPosition = positions[i];
}
else
{
// Animation : le mur monte progressivement
mur.transform.localScale = new Vector3(tailles[i].x, 0.01f, tailles[i].z);
mur.transform.localPosition = new Vector3(positions[i].x, 0.005f, positions[i].z);
}
if (mur.GetComponent<Renderer>() != null && matMur != null)
mur.GetComponent<Renderer>().sharedMaterial = matMur;
mur.isStatic = true;
mur.AddComponent<BoxCollider>();
if (!skipVisuel)
{
float elapsed = 0f;
while (elapsed < 0.6f)
{
elapsed += Time.deltaTime;
float t = 1f - Mathf.Pow(1f - elapsed / 0.6f, 3f);
float h = Mathf.Lerp(0.01f, tailles[i].y, t);
mur.transform.localScale = new Vector3(tailles[i].x, h, tailles[i].z);
mur.transform.localPosition = new Vector3(positions[i].x, h / 2f, positions[i].z);
yield return null;
}
mur.transform.localScale = tailles[i];
mur.transform.localPosition = positions[i];
yield return new WaitForSeconds(0.1f);
}
}
}
// ==================== PLAFOND ====================
IEnumerator ConstruirePlafond(Transform parent, float longueur, float largeur,
Vector3 origineExt, Vector3 origineSalle, bool skipVisuel)
{
float hauteurPlafond = _salleRef.hauteurPlafond;
float hauteurMurs = _salleRef.hauteurMurs;
float tailleDalle = _salleRef.tailleDallePlafond;
float epaisseurPlafond = _salleRef.epaisseurPlafond;
float espacement = _salleRef.espacementDalles;
float pas = tailleDalle + espacement;
Material matDalle = CreerMat(_salleRef.couleurPlafondDalle, 0f, 0.2f);
Material matSolide = CreerMat(_salleRef.couleurSousSol, 0f, 0.1f);
Material matRail = CreerMat(_salleRef.couleurPiedestal, 0.3f, 0.4f);
// Plafond solide — animation descente (ou direct en mode skip)
GameObject ps = GameObject.CreatePrimitive(PrimitiveType.Cube);
ps.name = "Ext_PlafondSolide";
ps.transform.SetParent(parent);
Vector3 posFinale = new Vector3(longueur / 2f, hauteurMurs, largeur / 2f);
if (skipVisuel)
{
ps.transform.localPosition = posFinale;
}
else
{
ps.transform.localPosition = new Vector3(longueur / 2f, hauteurMurs + 1f, largeur / 2f);
}
ps.transform.localScale = new Vector3(longueur, 0.1f, largeur);
if (ps.GetComponent<Renderer>() != null && matSolide != null)
ps.GetComponent<Renderer>().sharedMaterial = matSolide;
ps.isStatic = true;
if (!skipVisuel)
{
float elapsed = 0f;
while (elapsed < 0.8f)
{
elapsed += Time.deltaTime;
float t = elapsed / 0.8f; t = t * t * (3f - 2f * t);
ps.transform.localPosition = Vector3.Lerp(new Vector3(longueur / 2f, hauteurMurs + 1f, largeur / 2f), posFinale, t);
yield return null;
}
ps.transform.localPosition = posFinale;
}
// Dalle de fond plafond
GameObject fondPlaf = GameObject.CreatePrimitive(PrimitiveType.Cube);
fondPlaf.name = "Ext_FondPlafond";
fondPlaf.transform.SetParent(parent);
fondPlaf.transform.localPosition = new Vector3(longueur / 2f, hauteurPlafond + 0.001f, largeur / 2f);
fondPlaf.transform.localScale = new Vector3(longueur, epaisseurPlafond, largeur);
if (fondPlaf.GetComponent<Renderer>() != null && matDalle != null)
fondPlaf.GetComponent<Renderer>().sharedMaterial = matDalle;
fondPlaf.isStatic = true;
Collider fpc = fondPlaf.GetComponent<Collider>();
if (fpc != null) Destroy(fpc);
// === Dalles plafond alignées grille globale ===
GrilleInfo g = CalculerGrilleContinue(origineExt, origineSalle, pas, tailleDalle, longueur, largeur);
Debug.Log($"Extension Plafond: {g.nbX}x{g.nbZ} dalles");
for (int ix = 0; ix < g.nbX; ix++)
{
for (int iz = 0; iz < g.nbZ; iz++)
{
float px = g.startLocalX + ix * pas;
float pz = g.startLocalZ + iz * pas;
GameObject d = GameObject.CreatePrimitive(PrimitiveType.Cube);
d.name = $"Ext_PlafDalle_{ix}_{iz}";
d.transform.SetParent(parent);
d.transform.localPosition = new Vector3(px, hauteurPlafond, pz);
d.transform.localScale = new Vector3(tailleDalle - 0.02f, epaisseurPlafond, tailleDalle - 0.02f);
if (d.GetComponent<Renderer>() != null && matDalle != null)
d.GetComponent<Renderer>().sharedMaterial = matDalle;
d.isStatic = true;
Collider c = d.GetComponent<Collider>();
if (c != null) Destroy(c);
}
}
// Rails
float railStartX = g.startLocalX - tailleDalle / 2f;
float railStartZ = g.startLocalZ - tailleDalle / 2f;
for (int ix = 0; ix <= g.nbX; ix++)
{
float px = railStartX + ix * pas;
GameObject r = GameObject.CreatePrimitive(PrimitiveType.Cube);
r.name = "Ext_RailX_" + ix;
r.transform.SetParent(parent);
r.transform.localPosition = new Vector3(px, hauteurPlafond + epaisseurPlafond / 2f, largeur / 2f);
r.transform.localScale = new Vector3(0.02f, 0.03f, largeur);
if (r.GetComponent<Renderer>() != null && matRail != null)
r.GetComponent<Renderer>().sharedMaterial = matRail;
r.isStatic = true;
Collider c = r.GetComponent<Collider>();
if (c != null) Destroy(c);
}
for (int iz = 0; iz <= g.nbZ; iz++)
{
float pz = railStartZ + iz * pas;
GameObject r = GameObject.CreatePrimitive(PrimitiveType.Cube);
r.name = "Ext_RailZ_" + iz;
r.transform.SetParent(parent);
r.transform.localPosition = new Vector3(longueur / 2f, hauteurPlafond + epaisseurPlafond / 2f, pz);
r.transform.localScale = new Vector3(longueur, 0.03f, 0.02f);
if (r.GetComponent<Renderer>() != null && matRail != null)
r.GetComponent<Renderer>().sharedMaterial = matRail;
r.isStatic = true;
Collider c = r.GetComponent<Collider>();
if (c != null) Destroy(c);
}
}
// ==================== ÉCLAIRAGE ====================
IEnumerator ConstruireEclairage(Transform parent, float longueur, float largeur,
Vector3 origineExt, Vector3 origineSalle, bool skipVisuel)
{
float hauteurPlafond = _salleRef.hauteurPlafond;
float tailleDalle = _salleRef.tailleDallePlafond;
float espacement = _salleRef.espacementDalles;
int freqNeon = _salleRef.frequenceNeon;
float intensite = _salleRef.intensiteLumiere;
Color couleurLum = _salleRef.couleurLumiere;
Color couleurNeon = _salleRef.couleurNeon;
float pas = tailleDalle + espacement;
GrilleInfo g = CalculerGrilleContinue(origineExt, origineSalle, pas, tailleDalle, longueur, largeur);
// Calculer nbPlafondZ de la salle pour reproduire le même compteur linéaire
float salleL = _salleRef.longueur;
float salleW = _salleRef.largeur;
int nbPlafondZSalle = 0;
while ((nbPlafondZSalle * pas + tailleDalle / 2f) <= salleW + 0.001f) nbPlafondZSalle++;
List<Material> mats = new List<Material>();
List<Light> lts = new List<Light>();
for (int ix = 0; ix < g.nbX; ix++)
{
for (int iz = 0; iz < g.nbZ; iz++)
{
int globalX = g.gridOffsetX + ix;
int globalZ = g.gridOffsetZ + iz;
// Reproduire le compteur linéaire de la salle : globalX * nbZ + globalZ
// Pour les indices négatifs, on utilise Abs pour garder la cohérence
int globalCount = Mathf.Abs(globalX) * nbPlafondZSalle + Mathf.Abs(globalZ);
bool estNeon = (globalCount % freqNeon == 0);
if (estNeon)
{
float px = g.startLocalX + ix * pas;
float pz = g.startLocalZ + iz * pas;
GameObject neon = GameObject.CreatePrimitive(PrimitiveType.Cube);
neon.name = $"Ext_Neon_{ix}_{iz}";
neon.transform.SetParent(parent);
neon.transform.localPosition = new Vector3(px, hauteurPlafond, pz);
neon.transform.localScale = new Vector3(tailleDalle - 0.05f, 0.03f, tailleDalle - 0.05f);
Material m = new Material(Shader.Find("Standard"));
m.color = couleurNeon;
m.EnableKeyword("_EMISSION");
if (skipVisuel)
m.SetColor("_EmissionColor", couleurNeon * 2f); // tout de suite allume
else
m.SetColor("_EmissionColor", Color.black);
m.SetFloat("_Glossiness", 0.8f);
if (neon.GetComponent<Renderer>() != null)
neon.GetComponent<Renderer>().material = m;
neon.isStatic = true;
Collider c = neon.GetComponent<Collider>();
if (c != null) Destroy(c);
GameObject lo = new GameObject($"Ext_Light_{ix}_{iz}");
lo.transform.SetParent(parent);
lo.transform.localPosition = new Vector3(px, hauteurPlafond - 0.1f, pz);
Light l = lo.AddComponent<Light>();
l.type = LightType.Point;
l.color = couleurLum;
l.range = 4f;
l.shadows = LightShadows.Hard;
l.intensity = skipVisuel ? intensite : 0f;
lo.isStatic = true;
mats.Add(m);
lts.Add(l);
}
}
}
Debug.Log($"Extension Eclairage: {mats.Count} néons créés");
// Animation d'allumage progressif uniquement en mode non-skip
if (!skipVisuel)
{
Color emMax = couleurNeon * 2f;
for (int i = 0; i < mats.Count; i++)
{
float el = 0f;
while (el < 0.3f)
{
el += Time.deltaTime;
float t = el / 0.3f;
if (mats[i] != null) mats[i].SetColor("_EmissionColor", emMax * t);
if (lts[i] != null) lts[i].intensity = Mathf.Lerp(0f, intensite, t);
yield return null;
}
if (mats[i] != null) mats[i].SetColor("_EmissionColor", emMax);
if (lts[i] != null) lts[i].intensity = intensite;
yield return new WaitForSeconds(0.06f);
}
}
}
// ==================== GIZMOS ====================
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
if (!afficherGizmos) return;
SalleDatacenter salle = FindObjectOfType<SalleDatacenter>();
if (salle == null) return;
Vector3 salleOrigine = salle.transform.position;
float sL = salle.longueur;
float sW = salle.largeur;
Vector3 origineExt = CalculerOrigineNouvelleZoneEditor(salleOrigine, sL, sW);
float zoneL, zoneW;
if (direction == DirectionAgrandissement.Nord || direction == DirectionAgrandissement.Sud)
{ zoneL = nouvelleLargeur; zoneW = nouvelleProfondeur; }
else
{ zoneL = nouvelleProfondeur; zoneW = nouvelleLargeur; }
float hauteurSol = salle.hauteurSol;
float hMur = salle.hauteurMurs;
float ep = salle.epaisseurMur;
// Zone d'extension
Gizmos.color = couleurGizmoZone;
Vector3 centre = origineExt + new Vector3(zoneL / 2f, hauteurSol + 0.05f, zoneW / 2f);
Vector3 size = new Vector3(zoneL, 0.02f, zoneW);
Gizmos.DrawCube(centre, size);
Gizmos.color = new Color(couleurGizmoZone.r, couleurGizmoZone.g, couleurGizmoZone.b, 1f);
Gizmos.DrawWireCube(centre, size);
// Grille extension (damier)
float tailleDalle = salle.tailleDalle;
float espacement = salle.espacementDalles;
float pas = tailleDalle + espacement;
GrilleInfo g = CalculerGrilleContinue(origineExt, salleOrigine, pas, tailleDalle, zoneL, zoneW);
for (int ix = 0; ix < g.nbX; ix++)
{
for (int iz = 0; iz < g.nbZ; iz++)
{
float px = g.startLocalX + ix * pas;
float pz = g.startLocalZ + iz * pas;
Vector3 pos = origineExt + new Vector3(px, hauteurSol + 0.06f, pz);
int globalX = g.gridOffsetX + ix;
int globalZ = g.gridOffsetZ + iz;
bool foncee = (globalX + globalZ) % 2 == 1;
Gizmos.color = foncee
? new Color(0.3f, 0.3f, 0.3f, 0.6f)
: new Color(1f, 1f, 0.5f, 0.6f);
Gizmos.DrawCube(pos, new Vector3(tailleDalle * 0.9f, 0.01f, tailleDalle * 0.9f));
}
}
// Grille salle existante (wireframe)
int nbXSalle = Mathf.CeilToInt(sL / pas);
int nbZSalle = Mathf.CeilToInt(sW / pas);
Gizmos.color = new Color(0.2f, 0.6f, 1f, 0.15f);
for (int x = 0; x < nbXSalle; x++)
{
for (int z = 0; z < nbZSalle; z++)
{
float px = x * pas + tailleDalle / 2f;
float pz = z * pas + tailleDalle / 2f;
Vector3 pos = salleOrigine + new Vector3(px, hauteurSol + 0.07f, pz);
Gizmos.DrawWireCube(pos, new Vector3(tailleDalle * 0.85f, 0.005f, tailleDalle * 0.85f));
}
}
// Murs latéraux (preview)
Gizmos.color = new Color(1f, 0.4f, 0.2f, 0.25f);
switch (direction)
{
case DirectionAgrandissement.Ouest:
case DirectionAgrandissement.Est:
{
float murLen = zoneL - margeReculMurs;
float startX = (direction == DirectionAgrandissement.Ouest) ? 0 : margeReculMurs;
Gizmos.DrawCube(origineExt + new Vector3(startX + murLen / 2f, hMur / 2f, zoneW + ep / 2f), new Vector3(murLen, hMur, ep));
Gizmos.DrawCube(origineExt + new Vector3(startX + murLen / 2f, hMur / 2f, -ep / 2f), new Vector3(murLen, hMur, ep));
break;
}
case DirectionAgrandissement.Nord:
case DirectionAgrandissement.Sud:
{
float murLen = zoneW - margeReculMurs;
float startZ = (direction == DirectionAgrandissement.Sud) ? 0 : margeReculMurs;
Gizmos.DrawCube(origineExt + new Vector3(zoneL + ep / 2f, hMur / 2f, startZ + murLen / 2f), new Vector3(ep, hMur, murLen));
Gizmos.DrawCube(origineExt + new Vector3(-ep / 2f, hMur / 2f, startZ + murLen / 2f), new Vector3(ep, hMur, murLen));
break;
}
}
// Label
Vector3 labelPos = origineExt + new Vector3(zoneL / 2f, hMur + 0.5f, zoneW / 2f);
Handles.Label(labelPos,
$"Extension {direction}\n{zoneL}m x {zoneW}m\n" +
$"Grille: {g.nbX}x{g.nbZ} dalles\n" +
$"Start: ({g.startLocalX:F3}, {g.startLocalZ:F3})\n" +
$"Offset: ({offsetGrille.x:F3}, {offsetGrille.y:F3})\n" +
$"Recul: {margeReculMurs}m");
}
#endif
// ==================== EMPLACEMENTS EXTENSION ====================
/// <summary>
/// Calcule les zones d'allées froides pour l'extension (en local de l'extension).
/// Pour Est/Ouest : réutilise les mêmes positions Z que la salle d'origine.
/// Pour Nord/Sud : recalcule pour les nouvelles dimensions.
/// </summary>
List<Vector2> CalculerZonesAlleesFroidesExtension(float extLongueur, float extLargeur)
{
if (_salleRef == null) return new List<Vector2>();
// Pour Est/Ouest : si même largeur que la salle, réutiliser DIRECTEMENT
// les zones d'allées froides de la salle (même positions Z)
if (direction == DirectionAgrandissement.Est || direction == DirectionAgrandissement.Ouest)
{
if (Mathf.Abs(extLargeur - _salleRef.largeur) < 0.1f)
return _salleRef.CalculerZonesAlleesFroides();
}
// Sinon : recalculer pour les dimensions de l'extension
float profondeur = _salleRef.profondeurEmplacement;
float alleeFroide = _salleRef.espaceAlleeFroide;
float alleeChaude = _salleRef.espaceAlleeChaude;
float marge = _salleRef.margeDebutRangee;
float pasDalle = _salleRef.tailleDalle + _salleRef.espacementDalles;
float largeurPaire = profondeur * 2 + alleeFroide;
int nbPairesMax = Mathf.FloorToInt((extLargeur - marge * 2 + alleeChaude) / (largeurPaire + alleeChaude));
if (nbPairesMax < 1) nbPairesMax = 1;
int nbRangeesExt = nbPairesMax * 2;
float totalZ = nbPairesMax * largeurPaire + (nbPairesMax - 1) * alleeChaude;
float startZ = (extLargeur - totalZ) / 2f;
if (startZ < marge) startZ = marge;
startZ = Mathf.Round(startZ / pasDalle) * pasDalle;
List<Vector2> zones = new List<Vector2>();
float currentZ = startZ;
int rangeeNum = 1;
for (int paire = 0; paire < nbPairesMax; paire++)
{
if (rangeeNum <= nbRangeesExt)
{
rangeeNum++;
currentZ += profondeur;
}
zones.Add(new Vector2(currentZ, currentZ + alleeFroide));
currentZ += alleeFroide;
if (rangeeNum <= nbRangeesExt)
{
rangeeNum++;
currentZ += profondeur;
}
if (paire < nbPairesMax - 1)
currentZ += alleeChaude;
}
return zones;
}
/// <summary>
/// Génère les emplacements de baies dans l'extension.
/// Reprend la même logique que SalleDatacenter.GenererEmplacementsBaies()
/// mais adaptée aux dimensions de l'extension.
/// </summary>
void GenererEmplacementsExtension(Transform parent, float extLongueur, float extLargeur)
{
if (_salleRef == null) return;
float profondeur = _salleRef.profondeurEmplacement;
float alleeFroide = _salleRef.espaceAlleeFroide;
float alleeChaude = _salleRef.espaceAlleeChaude;
float largeurEmpl = _salleRef.largeurEmplacement;
float marge = _salleRef.margeDebutRangee;
float espBaies = _salleRef.espaceBaiesDansRangee;
float hauteurSol = _salleRef.hauteurSol;
float epaisseurDalle = _salleRef.epaisseurDalle;
float pasDalle = _salleRef.tailleDalle + _salleRef.espacementDalles;
GameObject parentEmpl = new GameObject("Ext_Emplacements");
parentEmpl.transform.SetParent(parent);
parentEmpl.transform.localPosition = Vector3.zero;
float largeurPaire = profondeur * 2 + alleeFroide;
// Pour Est/Ouest avec même largeur : réutiliser EXACTEMENT les mêmes paramètres
int nbPairesMax;
int nbRangeesExt;
float startZ;
bool memeAxeQueSalle = (direction == DirectionAgrandissement.Est || direction == DirectionAgrandissement.Ouest)
&& Mathf.Abs(extLargeur - _salleRef.largeur) < 0.1f;
if (memeAxeQueSalle)
{
// Même nombre de paires et même startZ que la salle
nbPairesMax = Mathf.CeilToInt(_salleRef.nbRangees / 2f);
nbRangeesExt = _salleRef.nbRangees;
float totalZSalle = nbPairesMax * largeurPaire + (nbPairesMax - 1) * alleeChaude;
bool rangeeImpaire = (_salleRef.nbRangees % 2 != 0);
if (rangeeImpaire) totalZSalle -= profondeur + alleeFroide;
startZ = (_salleRef.largeur - totalZSalle) / 2f;
if (startZ < marge) startZ = marge;
startZ = Mathf.Round(startZ / pasDalle) * pasDalle;
}
else
{
nbPairesMax = Mathf.FloorToInt((extLargeur - marge * 2 + alleeChaude) / (largeurPaire + alleeChaude));
if (nbPairesMax < 1) nbPairesMax = 1;
nbRangeesExt = nbPairesMax * 2;
float totalZ = nbPairesMax * largeurPaire + (nbPairesMax - 1) * alleeChaude;
startZ = (extLargeur - totalZ) / 2f;
if (startZ < marge) startZ = marge;
startZ = Mathf.Round(startZ / pasDalle) * pasDalle;
}
// Combien d'emplacements en X
int emplParRangee = Mathf.FloorToInt((extLongueur - marge * 2 + espBaies) / (largeurEmpl + espBaies));
if (emplParRangee < 1) emplParRangee = 1;
float totalXExt = emplParRangee * (largeurEmpl + espBaies) - espBaies;
float startX = (extLongueur - totalXExt) / 2f;
startX = Mathf.Round(startX / pasDalle) * pasDalle;
float currentZ = startZ;
int emplacementNum = 1;
int rangeeNum = 1;
float yEmplacement = hauteurSol + epaisseurDalle / 2f + 0.01f;
for (int paire = 0; paire < nbPairesMax; paire++)
{
if (rangeeNum <= nbRangeesExt)
{
GenererRangeeExtension(parentEmpl, startX, currentZ, 0f,
rangeeNum, emplParRangee, largeurEmpl, profondeur, espBaies,
yEmplacement, ref emplacementNum);
rangeeNum++;
currentZ += profondeur;
}
currentZ += alleeFroide;
if (rangeeNum <= nbRangeesExt)
{
GenererRangeeExtension(parentEmpl, startX, currentZ, 180f,
rangeeNum, emplParRangee, largeurEmpl, profondeur, espBaies,
yEmplacement, ref emplacementNum);
rangeeNum++;
currentZ += profondeur;
}
if (paire < nbPairesMax - 1)
currentZ += alleeChaude;
}
Debug.Log($"Extension: {emplacementNum - 1} emplacements en {rangeeNum - 1} rangées ({nbPairesMax} paires)");
}
void GenererRangeeExtension(GameObject parent, float startX, float posZ, float orientation,
int rangeeNum, int emplParRangee, float largeurEmpl, float profondeur,
float espBaies, float yEmplacement, ref int emplacementNum)
{
for (int i = 0; i < emplParRangee; i++)
{
float posX = startX + i * (largeurEmpl + espBaies) + largeurEmpl / 2f;
GameObject emplacement = GameObject.CreatePrimitive(PrimitiveType.Cube);
emplacement.name = "Ext_Empl_R" + rangeeNum + "_" + (i + 1);
emplacement.transform.SetParent(parent.transform);
emplacement.transform.localPosition = new Vector3(posX, yEmplacement, posZ + profondeur / 2f);
emplacement.transform.localScale = new Vector3(largeurEmpl, 0.01f, profondeur);
emplacement.transform.localRotation = Quaternion.identity;
// Layer Emplacement (comme dans SalleDatacenter)
int layerEmplacement = LayerMask.NameToLayer("Emplacement");
if (layerEmplacement >= 0) emplacement.layer = layerEmplacement;
EmplacementBaie empl = emplacement.AddComponent<EmplacementBaie>();
BoxCollider emplCol = emplacement.GetComponent<BoxCollider>();
if (emplCol != null) emplCol.isTrigger = true;
empl.numeroEmplacement = i + 1;
empl.rangee = rangeeNum;
empl.orientationFaceAvant = orientation;
empl.largeurEmplacement = largeurEmpl;
empl.profondeurEmplacement = profondeur;
// Label visuel : skip cote dedicated server (pas besoin du TextMesh)
if (!DedicatedServerMode.IsDedicatedServer)
{
GameObject numLabel = new GameObject("Ext_Num_" + emplacementNum);
numLabel.transform.SetParent(emplacement.transform);
numLabel.transform.localPosition = new Vector3(0, 0.01f, 0);
numLabel.transform.localRotation = Quaternion.Euler(90, 0, 0);
TextMesh tm = numLabel.AddComponent<TextMesh>();
tm.text = "E-R" + rangeeNum + "-" + (i + 1);
tm.fontSize = 24;
tm.characterSize = 0.04f;
tm.anchor = TextAnchor.MiddleCenter;
tm.alignment = TextAlignment.Center;
tm.color = new Color(0.3f, 0.6f, 0.9f, 0.5f);
}
emplacementNum++;
}
}
// ==================== HELPERS ====================
Vector3 GetDirectionVector()
{
switch (direction)
{
case DirectionAgrandissement.Nord: return Vector3.forward;
case DirectionAgrandissement.Sud: return Vector3.back;
case DirectionAgrandissement.Est: return Vector3.right;
case DirectionAgrandissement.Ouest: return Vector3.left;
default: return Vector3.forward;
}
}
Material CreerMat(Color c, float metallic = 0f, float gloss = 0.2f)
{
if (DedicatedServerMode.IsDedicatedServer) return null;
Material m = new Material(Shader.Find("Standard"));
m.color = c;
m.SetFloat("_Metallic", metallic);
m.SetFloat("_Glossiness", gloss);
return m;
}
void CreerTexte(Transform parent, string texte, Vector3 pos, int fontSize, float charSize, Color couleur)
{
if (DedicatedServerMode.IsDedicatedServer) return;
GameObject o = new GameObject("Txt_" + texte);
o.transform.SetParent(parent);
o.transform.localPosition = pos;
o.transform.localRotation = Quaternion.identity;
TextMesh tm = o.AddComponent<TextMesh>();
tm.text = texte;
tm.fontSize = fontSize;
tm.characterSize = charSize;
tm.anchor = TextAnchor.MiddleCenter;
tm.alignment = TextAlignment.Center;
tm.color = couleur;
tm.fontStyle = FontStyle.Bold;
// Rendre le texte visible des deux côtés
MeshRenderer mr = o.GetComponent<MeshRenderer>();
if (mr != null && mr.sharedMaterial != null)
{
mr.sharedMaterial.shader = Shader.Find("GUI/Text Shader");
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(AgrandissementSalle))]
public class AgrandissementSalleEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
AgrandissementSalle agr = (AgrandissementSalle)target;
GUILayout.Space(10);
SalleDatacenter salle = FindObjectOfType<SalleDatacenter>();
if (salle != null)
{
float tailleDalle = salle.tailleDalle;
float pas = tailleDalle + salle.espacementDalles;
Vector3 salleOrigine = salle.transform.position;
float sL = salle.longueur;
float sW = salle.largeur;
float zoneL, zoneW;
if (agr.direction == AgrandissementSalle.DirectionAgrandissement.Nord ||
agr.direction == AgrandissementSalle.DirectionAgrandissement.Sud)
{ zoneL = agr.nouvelleLargeur; zoneW = agr.nouvelleProfondeur; }
else
{ zoneL = agr.nouvelleProfondeur; zoneW = agr.nouvelleLargeur; }
Vector3 origineExt = agr.CalculerOrigineNouvelleZoneEditor(salleOrigine, sL, sW);
float deltaX = origineExt.x - salleOrigine.x;
float deltaZ = origineExt.z - salleOrigine.z;
EditorGUILayout.HelpBox(
$"Salle: {sL}x{sW}m @ ({salleOrigine.x:F2}, {salleOrigine.z:F2})\n" +
$"Extension: {zoneL}x{zoneW}m @ ({origineExt.x:F2}, {origineExt.z:F2})\n" +
$"Delta: ({deltaX:F3}, {deltaZ:F3})\n" +
$"Pas sol: {pas:F4}m | Pas plafond: {(salle.tailleDallePlafond + salle.espacementDalles):F4}m",
MessageType.Info);
}
GUILayout.Space(5);
GUI.backgroundColor = new Color(0.8f, 0.4f, 0.1f);
if (GUILayout.Button("Créer le panneau sur ce mur", GUILayout.Height(35)))
{
agr.CreerPanneau();
EditorUtility.SetDirty(agr);
}
GUI.backgroundColor = new Color(0.2f, 0.7f, 0.3f);
if (GUILayout.Button("Reset offset grille", GUILayout.Height(25)))
{
Undo.RecordObject(agr, "Reset offset grille");
agr.offsetGrille = Vector2.zero;
EditorUtility.SetDirty(agr);
}
GUI.backgroundColor = Color.white;
if (agr.estAgrandi)
EditorGUILayout.HelpBox("Déjà agrandie.", MessageType.Info);
}
}
#endif
// Helper simple pour exécuter des coroutines sur un GameObject stable
public class CoroutineRunner : MonoBehaviour { }