Система ключей (#7)

* new Dev map

* добавлена генерация узора ключей раундстартом

* каменная стена подгон KREKSa
фиксы

* add verbs to lock comp

* more Loc work

* some popups, logic to lock and unlock locks

* return old Dev

* ability to add locks into doors

* check to locked lock, to insert or remove locks

* doors examine

* key can uses directly to door

* really lockable!

* lockable crates!

* Bruh... Really big update

* Key sprites, long wall template

* KeyRingComponent

* lock entity start

* lockpicking!

* Shiny 1

* Update keys.yml

* Отображение только для держащего в руке

* Final

---------

Co-authored-by: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com>
This commit is contained in:
Ed
2024-04-02 20:59:40 +03:00
committed by GitHub
parent e7e235d2b7
commit ca4172c987
81 changed files with 1077 additions and 39 deletions

View File

@@ -0,0 +1,136 @@
using Content.Server.GameTicking.Events;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrystallPunk.LockKey;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Lock;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Server.CrystallPunk.LockKey;
public sealed partial class KeyholeGenerationSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly LockSystem _lock = default!;
private Dictionary<ProtoId<CPLockCategoryPrototype>, List<int>> _roundKeyData = new();
private const int DepthCompexity = 2;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<CPLockComponent, MapInitEvent>(OnLockInit);
SubscribeLocalEvent<CPKeyComponent, MapInitEvent>(OnKeyInit);
SubscribeLocalEvent<CPKeyComponent, ExaminedEvent>(OnKeyExamine);
}
#region Init
private void OnRoundStart(RoundStartingEvent ev)
{
_roundKeyData = new();
}
private void OnKeyInit(Entity<CPKeyComponent> keyEnt, ref MapInitEvent args)
{
if (keyEnt.Comp.AutoGenerateShape != null)
{
keyEnt.Comp.LockShape = GetKeyLockData(keyEnt.Comp.AutoGenerateShape.Value);
}
}
private void OnLockInit(Entity<CPLockComponent> lockEnt, ref MapInitEvent args)
{
if (lockEnt.Comp.AutoGenerateShape != null)
{
lockEnt.Comp.LockShape = GetKeyLockData(lockEnt.Comp.AutoGenerateShape.Value);
}
}
#endregion
private void OnKeyExamine(Entity<CPKeyComponent> key, ref ExaminedEvent args)
{
var parent = Transform(key).ParentUid;
if (parent != args.Examiner)
return;
if (key.Comp.LockShape == null)
return;
var markup = Loc.GetString("cp-lock-examine-key", ("item", MetaData(key).EntityName));
markup += " (";
foreach (var item in key.Comp.LockShape)
{
markup += $"{item} ";
}
markup += ")";
args.PushMarkup(markup);
}
private List<int> GetKeyLockData(ProtoId<CPLockCategoryPrototype> category)
{
if (_roundKeyData.ContainsKey(category))
return _roundKeyData[category];
else
{
var newData = GenerateNewUniqueLockData(category);
_roundKeyData[category] = newData;
return newData;
}
}
private List<int> GenerateNewUniqueLockData(ProtoId<CPLockCategoryPrototype> category)
{
List<int> newKeyData = new List<int>();
var categoryData = _proto.Index(category);
var ready = false;
var iteration = 0;
while (!ready)
{
//Generate try
newKeyData = new List<int>();
for (int i = 0; i < categoryData.Complexity; i++)
{
newKeyData.Add(_random.Next(-DepthCompexity, DepthCompexity));
}
//Identity Check shitcode
// На текущий момент он пытается сгенерировать уникальный код. Если он 100 раз не смог сгенерировать уникальный код, он выдаст последний сгенерированный неуникальный.
var unique = true;
foreach (var pair in _roundKeyData)
{
if (newKeyData.SequenceEqual(pair.Value))
{
unique = false;
break;
}
}
if (unique)
return newKeyData;
else
iteration++;
if (iteration > 100)
{
break;
}
}
Log.Error("The unique key for CPLockSystem could not be generated!");
return newKeyData; //FUCK
}
}

View File

@@ -1,7 +1,7 @@
using Content.Shared.Parallax.Biomes;
using Robust.Shared.Prototypes;
namespace Content.Server.Corvax.CrystallPunk.SpawnMapBiome;
namespace Content.Server.CrystallPunk.SpawnMapBiome;
/// <summary>
/// allows you to initialize a planet on a specific map at initialization time.

View File

@@ -5,7 +5,7 @@ using Content.Server.Station.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server.Corvax.CrystallPunk.SpawnMapBiome;
namespace Content.Server.CrystallPunk.SpawnMapBiome;
public sealed partial class StationBiomeSystem : EntitySystem
{
[Dependency] private readonly BiomeSystem _biome = default!;

View File

@@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.CrystallPunk.LockKey;
using Content.Server.Storage.Components;
using Content.Shared.Destructible;
using Content.Shared.Explosion;
@@ -36,7 +37,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
SubscribeLocalEvent<EntityStorageComponent, EntityUnpausedEvent>(OnEntityUnpausedEvent);
SubscribeLocalEvent<EntityStorageComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<EntityStorageComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract, after: new[] { typeof(LockSystem) });
SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract, after: new[] { typeof(LockSystem), typeof(KeyholeGenerationSystem) });
SubscribeLocalEvent<EntityStorageComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
SubscribeLocalEvent<EntityStorageComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
/// A prototype of the lock category. Need a roundstart mapping to ensure that keys and locks will fit together despite randomization.
/// </summary>
[Prototype("CPLockCategory")]
public sealed partial class CPLockCategoryPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary>
/// The number of elements that will be generated for the category.
/// </summary>
[DataField] public int Complexity = 3;
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
/// a key component that can be used to unlock and lock locks from CPLockComponent
/// </summary>
[RegisterComponent]
public sealed partial class CPKeyComponent : Component
{
[DataField]
public List<int>? LockShape = null;
/// <summary>
/// If not null, automatically generates a key for the specified category on initialization. This ensures that the lock will be opened with a key of the same category.
/// </summary>
[DataField]
public ProtoId<CPLockCategoryPrototype>? AutoGenerateShape = null;
}

View File

@@ -0,0 +1,9 @@
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
/// A component that allows you to track a ring of keys to quickly open and lock doors with the entire bunch.
/// </summary>
[RegisterComponent]
public sealed partial class CPKeyRingComponent : Component
{
}

View File

@@ -0,0 +1,37 @@
using Content.Shared.CrystallPunk.LockKey;
using Content.Shared.Damage;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
/// A component of a lock that stores its keyhole shape, complexity, and current state.
/// </summary>
[RegisterComponent]
public sealed partial class CPLockComponent : Component
{
[DataField]
public List<int>? LockShape = null;
[DataField]
public float LockPickDamageChance = 0.2f;
/// <summary>
/// On which element of the shape sequence the lock is now located. It's necessary for the mechanics of breaking and entering.
/// </summary>
[DataField]
public int LockpickStatus = 0;
/// <summary>
/// after a lock is broken into, it leaves a description on it that it's been tampered with.
/// </summary>
[DataField]
public bool LockpickeddFailMarkup = false;
/// <summary>
/// If not null, automatically generates a lock for the specified category on initialization. This ensures that the lock will be opened with a key of the same category.
/// </summary>
[DataField]
public ProtoId<CPLockCategoryPrototype>? AutoGenerateShape = null;
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.CrystallPunk.LockKey;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
/// A component of a lock that stores its keyhole shape, complexity, and current state.
/// </summary>
[RegisterComponent]
public sealed partial class CPLockpickComponent : Component
{
[DataField]
public int Health = 3;
[DataField]
public SoundSpecifier SuccessSound = new SoundPathSpecifier("/Audio/_CP14/Items/lockpick_use.ogg")
{
Params = AudioParams.Default
.WithVariation(0.05f)
.WithVolume(0.5f)
};
[DataField]
public SoundSpecifier FailSound = new SoundPathSpecifier("/Audio/_CP14/Items/lockpick_fail.ogg")
{
Params = AudioParams.Default
.WithVariation(0.05f)
.WithVolume(0.5f)
};
}

View File

@@ -0,0 +1,334 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Interaction;
using Content.Shared.Lock;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Serialization;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Verbs;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Storage;
using Robust.Shared.Random;
using Robust.Shared.Audio.Systems;
namespace Content.Shared.CrystallPunk.LockKey;
/// <summary>
///
/// </summary>
public sealed class SharedCPLockKeySystem : EntitySystem
{
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
private const int DepthCompexity = 2; //TODO - fix this constant duplication from KeyholeGenerationSystem.cs
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LockComponent, ContainerIsInsertingAttemptEvent>(OnLockInsertAttempt);
SubscribeLocalEvent<LockComponent, EntInsertedIntoContainerMessage>(OnLockInserted);
SubscribeLocalEvent<LockComponent, EntRemovedFromContainerMessage>(OnLockRemoved);
SubscribeLocalEvent<CPKeyComponent, AfterInteractEvent>(OnKeyInteract);
SubscribeLocalEvent<CPKeyRingComponent, AfterInteractEvent>(OnKeyRingInteract);
SubscribeLocalEvent<CPKeyComponent, GetVerbsEvent<UtilityVerb>>(OnKeyToLockVerb);
SubscribeLocalEvent<CPLockpickComponent, GetVerbsEvent<UtilityVerb>>(OnLockpickToLockVerb);
}
private void OnKeyRingInteract(Entity<CPKeyRingComponent> keyring, ref AfterInteractEvent args)
{
if (args.Handled)
return;
if (!args.CanReach || args.Target is not { Valid: true } target)
return;
if (!TryComp<StorageComponent>(keyring, out var storageComp))
return;
if (TryComp<LockComponent>(args.Target, out var lockComp) &&
TryGetLockFromSlot(args.Target.Value, out var lockEnt))
{
foreach (var item in storageComp.StoredItems)
{
if (!TryComp<CPKeyComponent>(item.Key, out var keyComp))
continue;
if (keyComp.LockShape != lockEnt.Value.Comp.LockShape)
continue;
TryUseKeyOnLock(args.User, args.Target.Value, new Entity<CPKeyComponent>(item.Key, keyComp), lockEnt.Value);
args.Handled = true;
return;
}
_popup.PopupEntity(Loc.GetString("cp-lock-keyring-use-nofit"), args.Target.Value, args.User);
}
}
private void OnKeyInteract(Entity<CPKeyComponent> key, ref AfterInteractEvent args)
{
if (args.Handled)
return;
if (!args.CanReach || args.Target is not { Valid: true } target)
return;
if (TryComp<LockComponent>(args.Target, out var lockComp) &&
TryGetLockFromSlot(args.Target.Value, out var lockEnt))
{
TryUseKeyOnLock(args.User, args.Target.Value, key, new Entity<CPLockComponent>(lockEnt.Value.Owner, lockEnt.Value.Comp));
args.Handled = true;
}
}
private void OnLockpickToLockVerb(Entity<CPLockpickComponent> lockpick, ref GetVerbsEvent<UtilityVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
if (!TryComp<LockComponent>(args.Target, out var lockComp) || !lockComp.Locked)
return;
if (!TryGetLockFromSlot(args.Target, out var lockItem))
return;
if (!TryComp<CPLockComponent>(lockItem, out var lockItemComp))
return;
var target = args.Target;
var user = args.User;
for (int i = DepthCompexity; i >= -DepthCompexity; i--)
{
var height = i;
var verb = new UtilityVerb()
{
Act = () =>
{
TryHackDoorElement(user, target, lockpick, lockItemComp, lockComp, height);
},
Text = Loc.GetString("cp-lock-verb-lockpick-use-text") + $" {height}",
Message = Loc.GetString("cp-lock-verb-lockpick-use-message"),
Category = VerbCategory.Lockpick,
Priority = height,
};
args.Verbs.Add(verb);
}
}
private bool TryHackDoorElement(EntityUid user, EntityUid target, Entity<CPLockpickComponent> lockpick, CPLockComponent lockEnt, LockComponent lockComp, int height)
{
if (lockEnt.LockShape == null)
return true;
if (height == lockEnt.LockShape[lockEnt.LockpickStatus]) //Success
{
_audio.PlayPvs(lockpick.Comp.SuccessSound, target);
lockEnt.LockpickStatus++;
if (lockEnt.LockpickStatus >= lockEnt.LockShape.Count) // Final success
{
if (lockComp.Locked)
{
_lock.TryUnlock(target, user, lockComp);
_popup.PopupEntity(Loc.GetString("cp-lock-unlock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
lockEnt.LockpickStatus = 0;
return true;
}
else
{
_lock.TryLock(target, user, lockComp);
_popup.PopupEntity(Loc.GetString("cp-lock-lock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
lockEnt.LockpickStatus = 0;
return true;
}
}
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-success"), target, user);
return true;
}
else //Fail
{
_audio.PlayPvs(lockpick.Comp.FailSound, target);
if (_random.Prob(lockEnt.LockPickDamageChance)) // Damage lockpick
{
lockpick.Comp.Health--;
if (lockpick.Comp.Health > 0)
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed-damage", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
} else
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed-break", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
QueueDel(lockpick);
}
}
else
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
}
lockEnt.LockpickeddFailMarkup = true;
lockEnt.LockpickStatus = 0;
return false;
}
}
private void OnKeyToLockVerb(Entity<CPKeyComponent> key, ref GetVerbsEvent<UtilityVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
if (!TryComp<LockComponent>(args.Target, out var lockComp))
return;
if (!TryGetLockFromSlot(args.Target, out var lockItem))
return;
if (!TryComp<CPLockComponent>(lockItem, out var lockItemComp))
return;
var target = args.Target;
var user = args.User;
var verb = new UtilityVerb()
{
Act = () =>
{
TryUseKeyOnLock(user, target, key, new Entity<CPLockComponent>(target, lockItemComp));
},
IconEntity = GetNetEntity(key),
Text = Loc.GetString(lockComp.Locked ? "cp-lock-verb-use-key-text-open" : "cp-lock-verb-use-key-text-close", ("item", MetaData(args.Target).EntityName)),
Message = Loc.GetString("cp-lock-verb-use-key-message", ("item", MetaData(args.Target).EntityName))
};
args.Verbs.Add(verb);
}
private void OnLockInsertAttempt(Entity<LockComponent> lockSlot, ref ContainerIsInsertingAttemptEvent args)
{
if (!lockSlot.Comp.Initialized)
return;
if (args.Container.ID != lockSlot.Comp.LockSlotId)
return;
if (!TryComp<CPLockComponent>(args.EntityUid, out var lockComp))
{
args.Cancel();
return;
}
if (lockComp == null)
return;
//if (lockComp.Locked)
//{
// _popup.PopupEntity(Loc.GetString(
// "cp-lock-lock-insert-fail-locked",
// ("lock", MetaData(args.EntityUid).EntityName),
// ("target", MetaData(lockSlot).EntityName)), lockSlot);
// args.Cancel();
//}
}
private void OnLockInserted(Entity<LockComponent> lockSlot, ref EntInsertedIntoContainerMessage args)
{
if (!lockSlot.Comp.Initialized)
return;
if (args.Container.ID != lockSlot.Comp.LockSlotId)
return;
if (!TryComp<CPLockComponent>(args.Entity, out var lockComp))
return;
_appearance.SetData(lockSlot, LockSlotVisuals.LockExist, true);
}
private void OnLockRemoved(Entity<LockComponent> lockSlot, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != lockSlot.Comp.LockSlotId)
return;
_appearance.SetData(lockSlot, LockSlotVisuals.LockExist, false);
}
public bool TryGetLockFromSlot(EntityUid uid,
[NotNullWhen(true)] out Entity<CPLockComponent>? lockEnt,
LockComponent? component = null)
{
if (!Resolve(uid, ref component, false))
{
lockEnt = null;
return false;
}
if (component.LockSlotId == null)
{
lockEnt = null;
return false;
}
if (_itemSlots.TryGetSlot(uid, component.LockSlotId, out ItemSlot? slot))
{
if (TryComp<CPLockComponent>(slot.Item, out var lockComp))
{
lockEnt = new Entity<CPLockComponent>(slot.Item.Value, lockComp);
return true;
}
else
{
lockEnt = null;
return false;
}
}
lockEnt = null;
return false;
}
private bool TryUseKeyOnLock(EntityUid user, EntityUid target, Entity<CPKeyComponent> keyEnt, Entity<CPLockComponent> lockEnt)
{
if (!TryComp<LockComponent>(target, out var lockComp))
return false;
var keyShape = keyEnt.Comp.LockShape;
var lockShape = lockEnt.Comp.LockShape;
if (keyShape == null || lockShape == null)
return false;
if (keyShape == lockShape)
{
if (lockComp.Locked)
{
if(_lock.TryUnlock(target, user))
_popup.PopupEntity(Loc.GetString("cp-lock-unlock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
}
else
{
if (_lock.TryLock(target, user))
_popup.PopupEntity(Loc.GetString("cp-lock-lock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
}
return true;
}
else
{
_popup.PopupEntity(Loc.GetString("cp-lock-key-use-nofit"), lockEnt, user);
}
return false;
}
}
[Serializable, NetSerializable]
public enum LockSlotVisuals : byte
{
LockExist
}

View File

@@ -18,7 +18,7 @@ public sealed partial class LockComponent : Component
/// </summary>
[DataField("locked"), ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public bool Locked = true;
public bool Locked = true;
/// <summary>
/// Whether or not the lock is toggled by simply clicking.
@@ -71,6 +71,12 @@ public sealed partial class LockComponent : Component
[DataField]
[AutoNetworkedField]
public TimeSpan UnlockTime;
/// <summary>
/// CrystallPunk LockSystem Adapt: we need LockEntity in object, in slotId.
/// </summary>
[DataField]
public string? LockSlotId = string.Empty;
}
/// <summary>

View File

@@ -1,6 +1,8 @@
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CrystallPunk.LockKey;
using Content.Shared.DoAfter;
using Content.Shared.Doors;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
@@ -26,6 +28,7 @@ public sealed class LockSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedCPLockKeySystem _lockCP = default!; //CrystallPunk Lock System Adapt
/// <inheritdoc />
public override void Initialize()
@@ -40,6 +43,7 @@ public sealed class LockSystem : EntitySystem
SubscribeLocalEvent<LockComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<LockComponent, LockDoAfter>(OnDoAfterLock);
SubscribeLocalEvent<LockComponent, UnlockDoAfter>(OnDoAfterUnlock);
SubscribeLocalEvent<LockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened); //CrystallPunk Lock System Adapt
}
private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args)
@@ -52,17 +56,28 @@ public sealed class LockSystem : EntitySystem
if (args.Handled)
return;
//CrystallPunk LockSystem Adapt
// Only attempt an unlock by default on Activate
if (lockComp.Locked)
{
TryUnlock(uid, args.User, lockComp);
args.Handled = true;
}
else if (lockComp.LockOnClick)
{
TryLock(uid, args.User, lockComp);
args.Handled = true;
}
//if (lockComp.Locked)
//{
// TryUnlock(uid, args.User, lockComp);
// args.Handled = true;
//}
//else if (lockComp.LockOnClick)
//{
// TryLock(uid, args.User, lockComp);
// args.Handled = true;
//}
//CrystallPunk LockSystem Adapt End
}
private void OnBeforeDoorOpened(EntityUid uid, LockComponent component, BeforeDoorOpenedEvent args)
{
if (!component.Locked)
return;
args.Cancel();
}
private void OnStorageOpenAttempt(EntityUid uid, LockComponent component, ref StorageOpenAttemptEvent args)
@@ -78,10 +93,22 @@ public sealed class LockSystem : EntitySystem
private void OnExamined(EntityUid uid, LockComponent lockComp, ExaminedEvent args)
{
args.PushText(Loc.GetString(lockComp.Locked
? "lock-comp-on-examined-is-locked"
: "lock-comp-on-examined-is-unlocked",
("entityName", Identity.Name(uid, EntityManager))));
//CrystallPunk Lock System Adapt Start
if (lockComp.LockSlotId != null && _lockCP.TryGetLockFromSlot(uid, out var lockEnt))
{
args.PushText(Loc.GetString("cp-lock-examine-lock-slot", ("lock", MetaData(lockEnt.Value).EntityName)));
args.PushMarkup(Loc.GetString(lockComp.Locked
? "lock-comp-on-examined-is-locked"
: "lock-comp-on-examined-is-unlocked",
("entityName", Identity.Name(uid, EntityManager))));
if (lockEnt.Value.Comp.LockpickeddFailMarkup)
args.PushMarkup(Loc.GetString("cp-lock-examine-lock-lockpicked", ("lock", MetaData(lockEnt.Value).EntityName)));
} else
{
args.PushText(Loc.GetString("cp-lock-examine-lock-null"));
}
//CrystallPunk Lock System Adapt End
}
/// <summary>
@@ -226,20 +253,24 @@ public sealed class LockSystem : EntitySystem
private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !CanToggleLock(uid, args.User))
return;
//CrystallPunk Lock System Adapt
AlternativeVerb verb = new()
{
Act = component.Locked ?
() => TryUnlock(uid, args.User, component) :
() => TryLock(uid, args.User, component),
Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"),
Icon = component.Locked ?
new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unlock.svg.192dpi.png")) :
new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")),
};
args.Verbs.Add(verb);
//if (!args.CanAccess || !args.CanInteract || !CanToggleLock(uid, args.User))
// return;
//
//AlternativeVerb verb = new()
//{
// Act = component.Locked ?
// () => TryUnlock(uid, args.User, component) :
// () => TryLock(uid, args.User, component),
// Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"),
// Icon = component.Locked ?
// new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unlock.svg.192dpi.png")) :
// new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")),
//};
//args.Verbs.Add(verb);
//CrystallPunk Lock System Adapt End
}
private void OnEmagged(EntityUid uid, LockComponent component, ref GotEmaggedEvent args)

View File

@@ -76,6 +76,9 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory InstrumentStyle =
new("verb-categories-instrument-style", null);
public static readonly VerbCategory Lockpick =
new("verb-categories-lockpick", "/Textures/Interface/VerbIcons/lock.svg.192dpi.png");
public static readonly VerbCategory ChannelSelect = new("verb-categories-channel-select", null);
public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null);

View File

@@ -0,0 +1,14 @@
- files: ["key_drop.ogg"]
license: "CC0-1.0"
copyright: 'Kyanite_ of Freesound.org. Mixed from stereo to mono.'
source: "https://freesound.org/people/Kyanite_/sounds/432913/"
- files: ["lockpick_use.ogg"]
license: "CC0-1.0"
copyright: 'by Fugeni of Freesound.org. Mixed from stereo to mono.'
source: "https://freesound.org/people/Fugeni/sounds/416286/"
- files: ["lockpick_fail.ogg"]
license: "CC0-1.0"
copyright: 'by SinusPi of Freesound.org. Cropped and mixed from stereo to mono.'
source: "https://freesound.org/people/SinusPi/sounds/545044/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,9 @@
lock-comp-on-examined-is-locked = [color=#a84032]Он заперт[/color].
lock-comp-on-examined-is-unlocked = [color=#32a834]Он открыт[/color].
lock-comp-do-lock-success = Вы заблокировали { $entityName }.
lock-comp-do-unlock-success = Вы разблокировали { $entityName }.
## ToggleLockVerb
toggle-lock-verb-unlock = Разблокировать
toggle-lock-verb-lock = Заблокировать

View File

@@ -0,0 +1,29 @@
cp-lock-slot-component-slot-name-default = Замок
cp-lock-examine-key = Высота зубчиков на ключе выстраивается в следующую последовательность:
cp-lock-examine-lock-slot = Здесь висит {$lock}.
cp-lock-examine-lock-null = Сюда можно прикрепить замок.
cp-lock-examine-lock-lockpicked = [color=#b57c3c]{$lock} выглядит потрепанным, словно в нем кто-то копался.[/color]
cp-lock-verb-use-key-text-open = Открыть {$item}
cp-lock-verb-use-key-text-close = Закрыть {$item}
cp-lock-verb-use-key-message = Использовать ключ, чтобы попытаться открыть или закрыть {$item}.
cp-lock-unlock-lock = Вы открыли {$lock}
cp-lock-lock-lock = Вы заблокировали {$lock}
cp-lock-key-use-nofit = Кажется, этот ключ сюда не подходит...
cp-lock-keyring-use-nofit = Кажется, ни один из ключей отсюда не подходит...
cp-lock-lock-insert-fail-locked = Сначала разблокируйте {$lock}, чтобы повесить его на {$target}
cp-lock-lock-remove-fail-locked = Сначала разблокируйте {$lock}, чтобы снять его с {$target}
cp-lock-target-use-failed-locked = {$target} не открывается
cp-lock-verb-lockpick-use-text = Имитировать:
cp-lock-verb-lockpick-use-message = Попытаться имитировать элемент формы ключа.
verb-categories-lockpick = Взлом
cp-lock-lockpick-success = Что-то в замке сдвигается...
cp-lock-lockpick-failed = {$lock} с громким щелчком встает на место.
cp-lock-lockpick-failed-damage = {$lock} с громким щелчком встает на место, надломив отмчычку.
cp-lock-lockpick-failed-break = {$lock} с громким щелчком встает на место, ломая вашу отмычку.

View File

@@ -394,25 +394,25 @@ entities:
- type: DayCycle
timeEntries:
- startColor: "#754a4a" #Рассвет
duration: 10
duration: 30
isNight: false
- startColor: "#e0ba87" #Полдень
duration: 10
duration: 30
isNight: false
- startColor: "#bfeeff" #Полдень
duration: 10
duration: 30
isNight: false
- startColor: "#385163" #Вечер
duration: 10
duration: 30
isNight: true
- startColor: "#060d12" #Ночь
duration: 10
duration: 30
isNight: true
- startColor: "#000000" #Ночь
duration: 10
duration: 30
isNight: true
- startColor: "#120906" #Ночь
duration: 10
duration: 30
isNight: false
- proto: AirAlarm
entities:

View File

@@ -0,0 +1,67 @@
- type: entity
parent: BaseMaterialDoorNavMap
id: CPBaseWoodDoor
name: деревянная дверь
description: Не самая прочная конструкция, но это лучше чем ничего.
suffix: Без замка
components:
- type: Sprite
sprite: Structures/Doors/MineralDoors/wood_door.rsi
layers:
- state: closed
map: ["enum.DoorVisualLayers.Base"]
- type: Door
bumpOpen: false
clickOpen: true
closeTimeOne: 0.2
closeTimeTwo: 0.6
openTimeOne: 0.6
openTimeTwo: 0.2
openSound:
path: /Audio/Effects/door_open.ogg
closeSound:
path: /Audio/Effects/door_close.ogg
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Wood
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Lock
locked: true
lockSlotId: lock_slot
- type: ItemSlots
slots:
lock_slot:
name: cp-lock-slot-component-slot-name-default
disableEject: true
insertOnInteract: false
ejectOnBreak: true
whitelist:
components:
- CPLock
- type: ContainerContainer
containers:
lock_slot: !type:ContainerSlot
- type: entity
parent: CPBaseWoodDoor
id: CPWoodDoorTavern
suffix: Таверна
components:
- type: ItemSlots
slots:
lock_slot:
name: cp-lock-slot-component-slot-name-default
startingItem: LockTavern
disableEject: true
insertOnInteract: false
ejectOnBreak: true
whitelist:
components:
- CPLock

View File

@@ -37,6 +37,20 @@
key: CPwallswood
base: wood
- type: entity
id: CaveStoneWall
name: каменная порода
parent: CPBaseWall
description: Прочная каменная природная стена. От нее веет пещерным холодом.
components:
- type: Sprite
sprite: CrystallPunk/Structures/Walls/cave_stone.rsi
- type: Icon
sprite: CrystallPunk/Structures/Walls/cave_stone.rsi
- type: IconSmooth
key: CPwallsbrick
base: stone
- type: entity
id: StoneRuinFragment
parent: BaseRock

View File

@@ -0,0 +1,41 @@
- type: entity
parent: BaseItem
id: BaseKeyRing
suffix: Пустое
name: кольцо для ключей
description: Позволяет комфортно хранить большое количество ключей в одном месте.
components:
- type: CPKeyRing
- type: Sprite
sprite: CrystallPunk/Objects/keys.rsi
layers:
- state: keyring
- state: ring-0
map: ["enum.StorageFillLayers.Fill"]
- type: Appearance
- type: StorageFillVisualizer
maxFillLevels: 4
fillBaseName: ring
- type: Storage
grid:
- 0,0,5,0
maxItemSize: Small
whitelist:
tags:
- CPKey
- type: UserInterface
interfaces:
- key: enum.StorageUiKey.Key
type: StorageBoundUserInterface
- type: ContainerContainer
containers:
storagebase: !type:Container
- type: entity
parent: BaseKeyRing
id: KeyRingInnkeeper
suffix: Трактирщик
components:
- type: StorageFill
contents:
- id: KeyTavern

View File

@@ -0,0 +1,42 @@
- type: entity
parent: BaseItem
id: BaseKey
abstract: true
name: ключ
description: Небольшая заковыристая железяка, открывающая определенные замки. Не отдавайте кому попало!
components:
- type: Tag
tags:
- CPKey
- type: Item
size: Tiny
- type: Sprite
sprite: CrystallPunk/Objects/keys.rsi
- type: CPKey
autoGenerateShape: Debug
- type: EmitSoundOnLand
sound:
path: /Audio/_CP14/Items/key_drop.ogg
params:
variation: 0.05
- type: entity
parent: BaseItem
id: BaseLockpick
name: отмычка
description: Воровской инструмент, при должном умении и сноровке позволяющий взламывать любые замки.
components:
- type: Sprite
sprite: CrystallPunk/Objects/keys.rsi
state: lockpick
- type: CPLockpick
- type: entity
parent: BaseKey
id: KeyTavern
name: ключ от таверны
components:
- type: Sprite
state: key1
- type: CPKey
autoGenerateShape: Tavern

View File

@@ -0,0 +1,22 @@
- type: entity
parent: BaseItem
abstract: true
id: BaseLock
name: стальной замок
description: Он запирает вещи. И вам потребуется ключ, чтобы открыть их обратно.
components:
- type: CPLock
lockPickDamageChance: 0.2
autoGenerateShape: Debug
- type: Sprite
sprite: CrystallPunk/Objects/keys.rsi
layers:
- state: lock
- type: entity
parent: BaseLock
id: LockTavern
name: замок от таверны
components:
- type: CPLock
autoGenerateShape: Tavern

View File

@@ -0,0 +1,7 @@
- type: CPLockCategory
id: Debug
complexity: 10
- type: CPLockCategory
id: Tavern
complexity: 3

View File

@@ -0,0 +1,2 @@
- type: Tag
id: CPKey

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

View File

@@ -8,7 +8,79 @@
"copyright": "Created by TheShuEd (Github) for CrystallPunk14",
"states": [
{
"name": "base"
"name": "key1"
},
{
"name": "key2"
},
{
"name": "key3"
},
{
"name": "key4"
},
{
"name": "key5"
},
{
"name": "key6"
},
{
"name": "key7"
},
{
"name": "key8"
},
{
"name": "key9"
},
{
"name": "key10"
},
{
"name": "key11"
},
{
"name": "key12"
},
{
"name": "key13"
},
{
"name": "key14"
},
{
"name": "key15"
},
{
"name": "key16"
},
{
"name": "key17"
},
{
"name": "key18"
},
{
"name": "lock"
},
{
"name": "lockpick"
},
{
"name": "keyring"
},
{
"name": "ring-0"
},
{
"name": "ring-1"
},
{
"name": "ring-2"
},
{
"name": "ring-3"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View File

@@ -0,0 +1,46 @@
{
"version": 1,
"size": {
"x": 32,
"y": 48
},
"license": "CC-BY-SA-3.0",
"copyright": "Created by TheShuEd (Discord) for CrystallPunk14",
"states": [
{
"name": "wood0",
"directions": 4
},
{
"name": "wood1",
"directions": 4
},
{
"name": "wood2",
"directions": 4
},
{
"name": "wood3",
"directions": 4
},
{
"name": "wood4",
"directions": 4
},
{
"name": "wood5",
"directions": 4
},
{
"name": "wood6",
"directions": 4
},
{
"name": "wood7",
"directions": 4
},
{
"name": "full"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,46 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Created by KREKS (Discord) for CrystallPunk14",
"states": [
{
"name": "stone0",
"directions": 4
},
{
"name": "stone1",
"directions": 4
},
{
"name": "stone2",
"directions": 4
},
{
"name": "stone3",
"directions": 4
},
{
"name": "stone4",
"directions": 4
},
{
"name": "stone5",
"directions": 4
},
{
"name": "stone6",
"directions": 4
},
{
"name": "stone7",
"directions": 4
},
{
"name": "full"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B