[ComponentEditorProps(category: "GameScripted/", description: "Voice Range Controller (Per Meter)")] class SCR_VoiceRangeControllerClass: ScriptComponentClass {} class SCR_VoiceRangeController: ScriptComponent { [Attribute("25", UIWidgets.Slider, "Start-Reichweite in Metern", "1 100 1")] protected int m_iCurrentRange; [Attribute("1", UIWidgets.Slider, "Minimale Reichweite", "1 100 1")] protected int m_iMinRange; [Attribute("100", UIWidgets.Slider, "Maximale Reichweite", "1 100 1")] protected int m_iMaxRange; [Attribute("1", UIWidgets.Slider, "Schrittgroesse (Meter pro Tastendruck)", "1 50 1")] protected int m_iStepSize; protected IEntity m_RangeRingEntity; protected int m_iLastInputTime = 0; static SCR_VoiceRangeController GetInstance() { if (!GetGame().GetPlayerController()) return null; return SCR_VoiceRangeController.Cast(GetGame().GetPlayerController().FindComponent(SCR_VoiceRangeController)); } override void OnPostInit(IEntity owner) { super.OnPostInit(owner); SetEventMask(owner, EntityEvent.INIT); } protected override void EOnInit(IEntity owner) { // Wir warten 1 Sekunde (1000ms), bevor wir die Tasten registrieren, // damit der PlayerController garantiert vollständig geladen ist. GetGame().GetCallqueue().CallLater(RegisterInputs, 1000, false, owner); } void RegisterInputs(IEntity owner) { PlayerController pc = PlayerController.Cast(owner); if (!pc) return; // Sind wir der lokale Spieler an diesem PC? if (pc != GetGame().GetPlayerController()) { return; } InputManager inputManager = GetGame().GetInputManager(); if (!inputManager) { Print("GTG Voice: FEHLER - Kein InputManager gefunden!", LogLevel.ERROR); return; } // Tasten registrieren inputManager.AddActionListener("GTG_RangeUp", EActionTrigger.DOWN, IncreaseRange); inputManager.AddActionListener("GTG_RangeDown", EActionTrigger.DOWN, DecreaseRange); Print("GTG Voice: ERFOLG - Tasten registriert und bereit!", LogLevel.WARNING); } void IncreaseRange() { if (System.GetTickCount() < m_iLastInputTime + 250) return; m_iLastInputTime = System.GetTickCount(); ChangeRange(1); } void DecreaseRange() { if (System.GetTickCount() < m_iLastInputTime + 250) return; m_iLastInputTime = System.GetTickCount(); ChangeRange(-1); } void ChangeRange(int direction) { int newRange = Math.ClampInt(m_iCurrentRange + (direction * m_iStepSize), m_iMinRange, m_iMaxRange); if (m_iCurrentRange == newRange) return; // Wir loggen immer, damit du siehst, ob das Skript arbeitet Print("GTG Voice: Taste erkannt. Neue Reichweite: " + newRange.ToString(), LogLevel.WARNING); m_iCurrentRange = newRange; ApplyRangeToVON(); UpdateUI(); DrawRangeCircle(); } protected void ApplyRangeToVON() { PlayerController pc = PlayerController.Cast(GetOwner()); if (!pc) return; IEntity player = pc.GetControlledEntity(); if (!player) return; SCR_VONController vonContr = SCR_VONController.Cast(GetOwner().FindComponent(SCR_VONController)); if (!vonContr) return; // Finde heraus, welche Komponente wir basierend auf den Metern brauchen typename compType = SCR_VoNNormal; if (m_iCurrentRange <= 15) compType = SCR_VoNWhispering; else if (m_iCurrentRange >= 50) compType = SCR_VoNLoud; SCR_VoNComponent newVonComp = SCR_VoNComponent.Cast(player.FindComponent(compType)); if (newVonComp) { // Nur wechseln, wenn sich die Stufe WIRKLICH ändert (verhindert unnötige Updates) if (vonContr.GetVONComponent() == newVonComp) return; // Komponente sanft wechseln OHNE den Toggle (behält Push-to-Talk und Mute-Status bei!) vonContr.SetVONComponent(newVonComp); Print("GTG Voice: VON-Stufe gewechselt zu: " + compType.ToString(), LogLevel.WARNING); } } void UpdateUI() { SCR_HUDManagerComponent hud = SCR_HUDManagerComponent.Cast(GetOwner().FindComponent(SCR_HUDManagerComponent)); if (!hud) return; SCR_VoiceRangeDisplay display = SCR_VoiceRangeDisplay.Cast(hud.FindInfoDisplay(SCR_VoiceRangeDisplay)); if (display) display.UpdateRangeText(m_iCurrentRange); } void DrawRangeCircle() { PlayerController pc = PlayerController.Cast(GetOwner()); // Sicherheitscheck: Nur beim lokalen Spieler zeichnen if (!pc || pc != GetGame().GetPlayerController()) return; IEntity player = pc.GetControlledEntity(); if (!player) return; if (m_RangeRingEntity) { SCR_EntityHelper.DeleteEntityAndChildren(m_RangeRingEntity); m_RangeRingEntity = null; } vector playerPos = player.GetOrigin(); playerPos[1] = playerPos[1] + 0.1; EntitySpawnParams params = new EntitySpawnParams(); params.TransformMode = ETransformMode.WORLD; params.Transform[3] = playerPos; ResourceName ringPrefab = "{E95CB8F4C4B2C210}Prefabs/Decals/Markings/Runway/Decal_Helipad_01.et"; Resource res = Resource.Load(ringPrefab); if (res && res.IsValid()) { m_RangeRingEntity = GetGame().SpawnEntityPrefab(res, GetGame().GetWorld(), params); if (m_RangeRingEntity) { float baseSize = 5.0; float finalScale = m_iCurrentRange / baseSize; m_RangeRingEntity.SetScale(finalScale); Print(string.Format("GTG Voice: Meter %1 -> Scale gesetzt auf %2", m_iCurrentRange, finalScale), LogLevel.WARNING); } } GetGame().GetCallqueue().Remove(HideRangeCircle); GetGame().GetCallqueue().CallLater(HideRangeCircle, 2000, false); } // DIESE FUNKTION HAT GEFEHLT: void HideRangeCircle() { if (m_RangeRingEntity) { SCR_EntityHelper.DeleteEntityAndChildren(m_RangeRingEntity); m_RangeRingEntity = null; } } }