class DigitalCodeLock : ItemBase
{
const string MEMPOINT_INSIDE = "inside";
const string MEMPOINT_CENTER = "ce_center";
protected const string ATTACHMENT_SLOT_BATTERY = "BatteryD";
protected const string SELECTION_NAME_BACK = "back";
protected ref CodeLockComponent m_CodeLockComponent;
protected ref CodeLockVisualManager m_VisualManager;
protected bool m_Reversed = false;
// Sounds
private const string CODELOCK_ACCESS_SOUND = "digitalLock_beep_Access_SoundSet";
private const string CODELOCK_LOCK_SOUND = "digitalLock_mechanism_Lock_SoundSet";
private const string CODELOCK_UNLOCK_SOUND = "digitalLock_mechanism_Unlock_SoundSet";
private const string CODELOCK_ERROR_SOUND = "digitalLock_beep_Error_SoundSet";
private const string CODELOCK_DIGITS_SOUND = "digitalLock_buttons_Digits_SoundSet";
private const string CODELOCK_CLEAR_SOUND = "digitalLock_button_Clear_SoundSet";
private const string CODELOCK_CONFIRM_SOUND = "digitalLock_button_Key_confirm_SoundSet";
void DigitalCodeLock()
{
Init();
}
protected void Init()
{
m_CodeLockComponent = new CodeLockComponent(this);
m_VisualManager = new CodeLockVisualManager(this, m_CodeLockComponent);
RegisterNetSyncVariableInt("m_CodeLockComponent.m_DoorIndex");
RegisterNetSyncVariableInt("m_CodeLockComponent.m_Stage");
RegisterNetSyncVariableInt("m_CodeLockComponent.m_WrongCodeCount");
RegisterNetSyncVariableInt("m_CodeLockComponent.m_UIAction");
RegisterNetSyncVariableBool("m_CodeLockComponent.m_IsLocked");
RegisterNetSyncVariableBool("m_CodeLockComponent.m_HasPIN");
RegisterNetSyncVariableBool("m_CodeLockComponent.m_HasBruteForceProtection");
RegisterNetSyncVariableBoolSignal("m_CodeLockComponent.m_HandleBruteForceProtection");
RegisterNetSyncVariableBool("m_Reversed");
}
override void EEInit()
{
super.EEInit();
// Set visuals on init
ForceFullVisualsUpdate();
}
override void EEItemLocationChanged(notnull InventoryLocation oldLoc, notnull InventoryLocation newLoc)
{
super.EEItemLocationChanged(oldLoc, newLoc);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
if (newLoc.GetType() != InventoryLocationType.ATTACHMENT)
{
if (g_Game.IsDedicatedServer())
SetSynchDirty();
// Force update since animation need too change
UpdateVisuals();
}
}
}
override void EEItemAttached(EntityAI item, string slot_name)
{
super.EEItemAttached(item, slot_name);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
//! Start energy consumption once battery is attached and the lock is already attached to a parent that can have a construction component
if (slot_name == ATTACHMENT_SLOT_BATTERY)
{
Battery9V battery = Battery9V.Cast(item);
if (battery)
{
float batteryEnergy = battery.GetCompEM().GetEnergy();
if (batteryEnergy > 0 && HasValidParent())
{
m_CodeLockComponent.SetStage(CodeLockStage.SETTING);
GetCompEM().SwitchOn();
}
UpdateVisuals();
}
}
}
}
override void EEItemDetached(EntityAI item, string slot_name)
{
super.EEItemDetached(item, slot_name);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
//! Stop energy consumption once battery is detached with active energy manager
if (slot_name == ATTACHMENT_SLOT_BATTERY)
{
m_CodeLockComponent.SetStage(CodeLockStage.NONE);
GetCompEM().SwitchOff();
UpdateVisuals();
}
}
}
override void OnWasAttached(EntityAI parent, int slot_id)
{
super.OnWasAttached(parent, slot_id);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
//! Start energy consumption once code lock is attached with a battery and the lock parent can have a construction component
string slotName = InventorySlots.GetSlotName(slot_id);
if (slotName.IndexOf("Att_CodeLock") > -1)
{
Battery9V battery = GetBattery();
if (battery && battery.GetCompEM().GetEnergy() > 0 && !GetCompEM().IsWorking())
{
m_CodeLockComponent.SetStage(CodeLockStage.SETTING);
GetCompEM().SwitchOn();
}
if (parent && parent.CanUseConstruction())
{
BuildingBase buildingBase = BuildingBase.Cast(parent);
if (buildingBase)
{
SetDoorInfoFromSlot(buildingBase, slotName);
}
}
}
UpdateVisuals();
}
}
override void OnWasDetached(EntityAI parent, int slot_id)
{
super.OnWasDetached(parent, slot_id);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
//! Stop energy consumption once lock is detached with active energy manager
if (GetCompEM().IsWorking())
GetCompEM().SwitchOff();
string slotName = InventorySlots.GetSlotName(slot_id);
if (slotName.IndexOf("Att_CodeLock") > -1)
{
m_Reversed = false;
m_CodeLockComponent.StopAllTimers();
m_CodeLockComponent.OnParentDetached();
}
UpdateVisuals();
}
}
override bool CanDetachAttachment(EntityAI parent)
{
if (IsLocked())
return false;
return true;
}
protected void SetDoorInfoFromSlot(BuildingBase buildingBase, string slotName)
{
map<int, ref AdditionalDoorInfo> doorsInfoMap = buildingBase.GetDoorInfoHolder().GetDoorInfoMap();
//! Get door index of the door this lock can manipulate fo ther attached lock based on related attachment slot from AdditionalDoorInfosHolder data
if (doorsInfoMap && doorsInfoMap.Count() > 0)
{
int doorIndex = -1;
AdditionalDoorInfo doorInfo;
foreach(int doorInfoIndex, AdditionalDoorInfo doorInfoData: doorsInfoMap)
{
foreach(string relatedSlotName: doorInfoData.m_RelatedInventorySlotNames)
{
if (relatedSlotName == slotName)
{
doorIndex = doorInfoIndex;
doorInfo = doorInfoData;
break;
}
}
if (doorIndex != -1)
break;
}
if (doorIndex > -1)
{
m_CodeLockComponent.SetDoorIndex(doorIndex);
}
//! Check for door part rotation
Rebuilding rebuilding = Rebuilding.Cast(buildingBase.GetConstructionBasic());
if (rebuilding && doorInfo)
{
ConstructionPart constructionPart = rebuilding.GetConstructionPart(doorInfo.m_DoorConstructionPartName);
ConstructionPartRebuildReversibleDoor reversibleDoorPart;
if (constructionPart && Class.CastTo(reversibleDoorPart, constructionPart))
{
m_Reversed = reversibleDoorPart.IsReversedLocal();
}
}
}
}
//! Condition check called from ActionTakeItem and ActionTakeItemToHands
bool CanDetachCodeLock(notnull PlayerBase player, int componentIndex)
{
if (HasValidParent() && !IsLocked())
{
//! GetReversedState is used to check if code lock is mounted on reversible doors
if (!GetReversedState() && !MiscGameplayFunctions.MemPointDirectionalCheck(player, this, DigitalCodeLock.MEMPOINT_CENTER, DigitalCodeLock.MEMPOINT_INSIDE))
return false;
}
return true;
}
override void OnWorkStop()
{
super.OnWorkStop();
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
m_CodeLockComponent.StopAllTimers();
m_CodeLockComponent.OnParentReset(false);
ForceFullVisualsUpdate();
}
}
override void OnWorkStart()
{
super.OnWorkStart();
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
UpdateVisuals();
}
}
override void OnWork(float consumed_energy)
{
super.OnWork(consumed_energy);
if (g_Game.IsServer() || !g_Game.IsMultiplayer()) // single player or server side multiplayer
{
Battery9V battery = GetBattery();
if (battery)
{
if (GetCompEM().IsWorking() && battery.GetCompEM().GetEnergy() <= 0)
{
GetCompEM().SwitchOff();
m_CodeLockComponent.SetLockState(false, false);
}
float energyPercent = GetBatteryEnergy();
if (m_VisualManager.HasBatteryLevelChanged())
{
UpdateVisuals();
SetSynchDirty();
}
}
}
}
//! Unlock AND drop
void UnlockOnServer(EntityAI parent)
{
InventoryLocation inventoryLocation = new InventoryLocation;
GetInventory().GetCurrentInventoryLocation(inventoryLocation);
//drop entity from attachment slot
if (g_Game.IsMultiplayer())
{
parent.GetInventory().DropEntity(InventoryMode.SERVER, parent, this);
}
else
{
parent.GetInventory().DropEntity(InventoryMode.LOCAL, parent, this);
}
vector groundPos;
string slotName = InventorySlots.GetSlotName(inventoryLocation.GetSlot());
if (parent.MemoryPointExists(slotName))
{
groundPos = parent.ModelToWorld(parent.GetMemoryPointPos(slotName));
}
else
{
groundPos = GetPosition();
}
SetPosition(groundPos);
PlaceOnSurface();
}
Battery9V GetBattery()
{
Battery9V battery = Battery9V.Cast(GetInventory().FindAttachment(Battery9V.SLOT_ID));
return battery;
}
bool CanBeLocked()
{
return true;
}
bool CanBeUnlocked()
{
return true;
}
bool CanSetCode()
{
return true;
}
bool HasBattery()
{
return GetBattery() != null;
}
bool NeedBattery()
{
return true;
}
bool HasEnergy()
{
return GetCompEM().IsWorking();
}
bool HasPIN()
{
return m_CodeLockComponent.HasPIN();
}
string GetInputPIN()
{
return m_CodeLockComponent.GetInputPIN();
}
string GetLockPIN()
{
return m_CodeLockComponent.GetLockPIN();
}
void ResetPIN()
{
m_CodeLockComponent.ResetPIN();
}
void SetLockState(bool state)
{
m_CodeLockComponent.SetLockState(state);
}
CodeLockComponent GetCodeLockComponent()
{
return m_CodeLockComponent;
}
bool IsLocked()
{
return m_CodeLockComponent.IsLocked();
}
CodeLockStage GetStage()
{
return m_CodeLockComponent.GetStage();
}
bool HasBruteForceProtection()
{
return m_CodeLockComponent.HasBruteForceProtection();
}
bool ShouldHandleBruteForce()
{
return m_CodeLockComponent.ShouldHandleBruteForce();
}
int GetWrongCodeCount()
{
return m_CodeLockComponent.GetWrongCodeCount();
}
int GetMinLockPINLength()
{
return m_CodeLockComponent.GetMinLockPINLength();
}
bool GetReversedState()
{
return m_Reversed;
}
//! Checks whether the given player is standing on the physical (non-keypad) side of the lock
//! Fails closed when the required memory points are not available
bool IsPlayerOnBackside(PlayerBase player)
{
if (!player)
return false;
if (!MemoryPointExists(MEMPOINT_CENTER) || !MemoryPointExists(MEMPOINT_INSIDE))
return false;
bool playerOnInsideSide = MiscGameplayFunctions.MemPointDirectionalCheck(player, this, MEMPOINT_CENTER, MEMPOINT_INSIDE);
return playerOnInsideSide != GetReversedState();
}
bool HasValidParent()
{
EntityAI hierarchyParent = GetHierarchyRoot();
if (hierarchyParent.CanUseConstruction())
return true;
if (BroadcastedBunker.Cast(hierarchyParent))
return true;
return false;
}
CodeLockVisualManager GetVisualManager()
{
return m_VisualManager;
}
float GetBatteryEnergy()
{
Battery9V battery = GetBattery();
if (!battery)
return 0.0;
float energy = battery.GetCompEM().GetEnergy();
float energyMax = battery.GetCompEM().GetEnergyMax();
if (energyMax <= 0)
return 0.0;
return (energy / energyMax) * 100.0;
}
void UpdateVisuals()
{
if (m_VisualManager)
m_VisualManager.UpdateVisuals();
}
void ForceFullVisualsUpdate()
{
if (m_VisualManager)
{
m_VisualManager.MarkDirty();
m_VisualManager.UpdateVisuals();
}
}
override void SetActions()
{
super.SetActions();
AddAction(ActionAttachToConstruction);
}
override void OnRPC(PlayerIdentity sender, int rpc_type,ParamsReadContext ctx)
{
super.OnRPC(sender, rpc_type, ctx);
m_CodeLockComponent.OnRPC(sender, rpc_type, ctx);
}
override void OnStoreSave(ParamsWriteContext ctx)
{
super.OnStoreSave(ctx);
m_CodeLockComponent.OnStoreSave(ctx);
}
override bool OnStoreLoad(ParamsReadContext ctx, int version)
{
if (!super.OnStoreLoad(ctx, version))
return false;
if (!m_CodeLockComponent.OnStoreLoad(ctx, version))
return false;
return true;
}
override void AfterStoreLoad()
{
super.AfterStoreLoad();
if (HasBattery())
{
if (HasPIN())
{
m_CodeLockComponent.SetStage(CodeLockStage.NORMAL);
}
else
{
m_CodeLockComponent.SetStage(CodeLockStage.SETTING);
}
}
ForceFullVisualsUpdate();
}
override void EEHealthLevelChanged(int oldLevel, int newLevel, string zone)
{
super.EEHealthLevelChanged(oldLevel,newLevel,zone);
ForceFullVisualsUpdate();
}
override void OnVariablesSynchronized()
{
super.OnVariablesSynchronized();
if (m_CodeLockComponent)
{
CodeLockUIAction uiAction = m_CodeLockComponent.GetUIAction();
if (uiAction != CodeLockUIAction.NONE)
{
m_CodeLockComponent.CheckUIActionClient();
}
}
}
override bool IsExternalLockType()
{
return true;
}
override void InitItemSounds()
{
super.InitItemSounds();
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_ACCESS, CODELOCK_ACCESS_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_LOCK, CODELOCK_LOCK_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_UNLOCK, CODELOCK_UNLOCK_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_ERROR, CODELOCK_ERROR_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_CONFIRM_INPUT, CODELOCK_CONFIRM_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_CLEAR_INPUT, CODELOCK_CLEAR_SOUND);
GetItemSoundHandler().AddSound(SoundConstants.ITEM_DIGITAL_LOCK_DIGIT_INPUT, CODELOCK_DIGITS_SOUND);
}
//Debug menu Spawn Ground Special
override void OnDebugSpawn()
{
GetInventory().CreateInInventory("Battery9V");
}
}
class DigitalCodeLock_Bunker : DigitalCodeLock
{
override void Init()
{
super.Init();
BunkerBroadcastConfigInstance bunkerConfig = CfgBunkerBroadcastHandler.GetData();
int pinCodeLength = CodeLockComponent.MIN_LOCK_PIN_LENGTH;
if (bunkerConfig && bunkerConfig.bunkerSettings)
{
//! Make sure we are not overflow max. possible PIN code lenght
pinCodeLength = bunkerConfig.bunkerSettings.lockCodeLength;
if (pinCodeLength > CodeLockComponent.MAX_LOCK_PIN_LENGTH)
pinCodeLength = CodeLockComponent.MAX_LOCK_PIN_LENGTH;
}
m_CodeLockComponent.SetMinimalPinCodeLength(pinCodeLength);
}
override bool CanBeLocked()
{
return false;
}
override bool CanSetCode()
{
return false;
}
override bool CanDetachAttachment(EntityAI parent)
{
return false;
}
//! overrides the base behavior - intentional
override void OnWasDetached(EntityAI parent, int slot_id) {}
override bool NeedBattery()
{
return false;
}
override bool HasBattery()
{
return true;
}
override bool HasEnergy()
{
return true;
}
override bool ShowZonesHealth()
{
return true;
}
bool SetLockCode(string pin)
{
return m_CodeLockComponent.SetLockCode(pin);
}
void Disable()
{
m_CodeLockComponent.SetStage(CodeLockStage.NONE);
SetAnimationPhase(CodeLockVisualManager.CODELOCK_ANIMATED_GREEN_LIGHT, 1.0);
SetAnimationPhase(CodeLockVisualManager.CODELOCK_ANIMATED_RED_LIGHT, 1.0);
}
}