diff --git a/Content.Client/Imperial/Medieval/ShipDrowning/ClientShipDrowningSystem.cs b/Content.Client/Imperial/Medieval/ShipDrowning/ClientShipDrowningSystem.cs new file mode 100644 index 00000000000..3aa059d0fc5 --- /dev/null +++ b/Content.Client/Imperial/Medieval/ShipDrowning/ClientShipDrowningSystem.cs @@ -0,0 +1,3 @@ +// тут мог бы быть ваш код) +// но я криворукий бэкэндер который в душе не чает как работать нормально с визуалом поэтому тут пусто +// TODO : сделать нормальное отображение потопления, а не тот костыль который я сделаю diff --git a/Content.Server/Imperial/Medieval/Barrier/MagicBarrierComponent.cs b/Content.Server/Imperial/Medieval/Barrier/MagicBarrierComponent.cs index 225749c267e..ad4d8d58e44 100644 --- a/Content.Server/Imperial/Medieval/Barrier/MagicBarrierComponent.cs +++ b/Content.Server/Imperial/Medieval/Barrier/MagicBarrierComponent.cs @@ -1,3 +1,6 @@ +using System.Numerics; +using Content.Server.Imperial.Medieval.Ships.Sea.Init; +using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Prototypes; @@ -21,15 +24,19 @@ public sealed partial class MagicBarrierComponent : Component [DataField] public float MaxStability = 60f; + [DataField] public float Lose = 0.7f; + [DataField] public float Rate = 1.5f; + [DataField] public int Cycle = 0; [DataField, ViewVariables(VVAccess.ReadOnly)] public string EffectSoundOnScrollAdd = "/Audio/Imperial/Medieval/scroll_use.ogg"; + [DataField, ViewVariables(VVAccess.ReadOnly)] public string EffectSoundOnFinish = "/Audio/Imperial/Medieval/magic_craft.ogg"; @@ -49,5 +56,15 @@ public sealed partial class MagicBarrierComponent : Component [DataField] public Dictionary ReviveCount = new(); + + // Приветик, делаем генерацию морей + [DataField] + public bool SeaInitalazed = false; + + + [DataField] + public SeaMatrix? SeaMatrix = null; } } + + diff --git a/Content.Server/Imperial/Medieval/Ships/Anchor/ServerMedievalAnchorSystem.cs b/Content.Server/Imperial/Medieval/Ships/Anchor/ServerMedievalAnchorSystem.cs new file mode 100644 index 00000000000..9805e4d15ba --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Anchor/ServerMedievalAnchorSystem.cs @@ -0,0 +1,47 @@ +using Content.Server.Shuttles.Components; +using Content.Server.Shuttles.Systems; +using Content.Shared.Imperial.Medieval.Ships.Anchor; + +namespace Content.Server.Imperial.Medieval.Ships.Anchor; + +/// +/// This handles... +/// +public sealed class ServerMedievalAnchorSystem : EntitySystem +{ + [Dependency] private readonly ShuttleSystem _shuttleSystem = default!; + /// + public override void Initialize() + { + SubscribeLocalEvent(OnUseAnchor); + } + + private void OnUseAnchor(EntityUid uid, MedievalAnchorComponent component, UseAnchorEvent args) + { + if (args.Target == null || args.Cancelled) + return; + + var target = component.Owner; + + var enabled = component.Enabled; + + ShuttleComponent? shuttleComponent = default; + + var transform = Transform(target); + var grid = transform.GridUid; + if (!grid.HasValue || !transform.Anchored || !Resolve(grid.Value, ref shuttleComponent)) + return; + + if (!enabled) + { + _shuttleSystem.Disable(grid.Value); + } + else + { + _shuttleSystem.Enable(grid.Value); + } + + shuttleComponent.Enabled = !enabled; + component.Enabled = !enabled; + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Helm/HelmSystem.cs b/Content.Server/Imperial/Medieval/Ships/Helm/HelmSystem.cs new file mode 100644 index 00000000000..44516d3203a --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Helm/HelmSystem.cs @@ -0,0 +1,75 @@ +using System.Numerics; +using Content.Server.Administration.Logs; +using Content.Shared._RD.Weight.Systems; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Popups; +using NetCord; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.Helm; + +/// +/// This handles... +/// +public sealed class HelmSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly RDWeightSystem _rdWeight = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + + public void RotateShip(EntityUid boat, EntityUid helm, float helmForce) + { + _physics.WakeBody(boat); + var helmAngle = _transform.GetWorldRotation(helm); + var entities = _lookup.GetEntitiesIntersecting(boat); + EntityUid? steeringOar = null; + foreach (var e in entities) + { + if (HasComp(e)) + { + steeringOar = e; + break; + } + } + if (steeringOar == null) + return; + var steeringOarAngle = _transform.GetWorldRotation(steeringOar.Value); + var diff = (float)steeringOarAngle*180 - (float)helmAngle*180; + diff *= -1; + if (helmForce > 0) + { + _physics.ApplyAngularImpulse(boat, diff); + return; + } + if (helmForce < 0) + { + _physics.ApplyAngularImpulse(boat, -diff); + return; + } + } + + public float CheckForce(EntityUid boat, EntityUid helm) + { + var boatAngle = (float)_transform.GetWorldRotation(boat)*180; + var helmcos = MathF.Cos(boatAngle); + var helmsin = MathF.Sin(boatAngle); + var helmVector = _physics.GetMapLinearVelocity(boat); + var helmForce = Math.Abs(helmVector.X) + Math.Abs(helmVector.Y); + return helmForce; + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Helm/SteeringOarComponent.cs b/Content.Server/Imperial/Medieval/Ships/Helm/SteeringOarComponent.cs new file mode 100644 index 00000000000..16c657879f7 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Helm/SteeringOarComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Imperial.Medieval.Ships.Helm; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class SteeringOarComponent : Component +{ + +} diff --git a/Content.Server/Imperial/Medieval/Ships/Oar/ServerOarSystem.cs b/Content.Server/Imperial/Medieval/Ships/Oar/ServerOarSystem.cs new file mode 100644 index 00000000000..ffa1ae86c51 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Oar/ServerOarSystem.cs @@ -0,0 +1,131 @@ +using System.Numerics; +using Content.Shared._RD.Weight.Components; +using Content.Shared._RD.Weight.Systems; +using Content.Shared.Coordinates; +using Content.Shared.DoAfter; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; + + +using Content.Shared.Database; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Imperial.Medieval.Ships.Oar; +using Content.Shared.Interaction.Components; +using Content.Shared.Movement.Components; + + +namespace Content.Server.Imperial.Medieval.Ships.Oar; + +/// +/// This handles... +/// +public sealed class OarSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly RDWeightSystem _rdWeight = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnOarDoAfter); + } + + private void OnOarDoAfter(EntityUid uid, OarComponent component, ref OnOarDoAfterEvent args) + { + if (args.Cancelled) + { + RemComp(args.User); + RemComp(args.User); + } + + var item = _hands.GetActiveItem(args.User); + if (args.Cancelled || args.Handled || item == null) + return; + + if (!TryComp(item, out var comp)) + return; + + Push(item.Value, comp.Direction, comp.Power, args.User); + args.Handled = true; + args.Repeat = true; + } + + private void Push(EntityUid item, Angle direction, float power, EntityUid player) + { + power += power * (-10 + _skills.GetSkillLevel(player, "Strength")) * 0.1f; + + var boat = _transform.GetParentUid(player); + + var boatAngle = _transform.GetWorldRotation(boat); + + var playerAngle = _transform.GetWorldRotation(player); + + var entities = _lookup.GetEntitiesIntersecting(boat); + + if (entities.Count > 1000) + return; + + var weight = _rdWeight.GetTotal(boat); + + foreach (var entity in entities) + { + if (HasComp(entity)) + weight += _rdWeight.GetTotal(entity); + } + + if (weight == 0) + weight = 10; + + var directionVec = Vector2.Zero; + while (direction > 2) + { + direction -= 2; + } + + + direction += Angle.FromDegrees(45); + // от 0 до 0.5 (1,0) + // от 0.5 до 1 (0,1) + // от 1 до 1,5 (0,-1) + // от 1,5 до 0 (-1,0) + if (direction > 1) + { + if (direction > 1.5) + directionVec = new Vector2(-1,0); + else + { + directionVec = new Vector2(0,-1); + } + } + else + { + if (direction > 0.5) + directionVec = new Vector2(0,1); + else + { + directionVec = new Vector2(1,0); + } + } + + directionVec = playerAngle.RotateVec(directionVec); + var impulse = directionVec * (power / weight); + var angleimpulse = (power / weight); + + if (EntityManager.TryGetComponent(boat, out PhysicsComponent? body)) + { + _physics.WakeBody(boat); + _physics.ApplyLinearImpulse(boat, impulse, body: body); + // _physics.ApplyAngularImpulse(boat, angleimpulse, body: body); + } + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/DrownerComponent.cs b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/DrownerComponent.cs new file mode 100644 index 00000000000..5372d9621df --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/DrownerComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Imperial.Medieval.Ships.PlayerDrowning; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class DrownerComponent : Component +{ +} diff --git a/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningComponent.cs b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningComponent.cs new file mode 100644 index 00000000000..282e8fea95e --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Imperial.Medieval.Ships.PlayerDrowning; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class PlayerDrowningComponent : Component +{ + [DataField("drownTime")] + public int DrownTime; + + [DataField("Undrowable")] + public bool Undrowable; +} diff --git a/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningSystem.cs b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningSystem.cs new file mode 100644 index 00000000000..176bd6e6818 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/PlayerDrowningSystem.cs @@ -0,0 +1,101 @@ +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.PlayerDrowning; + +/// +/// This handles... +/// +public sealed class PlayerDrowningSystem : EntitySystem +{ + private const float DefaultReloadTimeSeconds = 1f; + private const int DrownTimeMax = 15; + private TimeSpan _nextCheckTime; + + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedStaminaSystem _staminaSystem = default!; + + /// + public override void Initialize() + { + base.Initialize(); + _nextCheckTime = _timing.CurTime + TimeSpan.FromSeconds(DefaultReloadTimeSeconds); + + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(DefaultReloadTimeSeconds); + + foreach (var component in EntityManager.EntityQuery()) + { + if (!TryComp(component.Owner, out var transform)) + continue; + var childBasement = transform.ChildEnumerator; + while (childBasement.MoveNext(out var childUid)) + EnsureComp(childUid); + } + + foreach (var component in EntityManager.EntityQuery()) + { + if (HasComp(_transform.GetParentUid(component.Owner))) + { + ProcessDrowning(component.Owner); + continue; + } + if (component.DrownTime > 0) + component.DrownTime -= 1; + else + RemComp(component.Owner); + + } + + } + } + + private void ProcessDrowning(EntityUid uid) + { + if (HasComp(uid)) + return; + + if (!TryComp(uid, out var drowner)) + EnsureComp(uid); + else + { + if (drowner.Undrowable) + return; + if (HasComp(uid)) + return; + + if (TryComp(uid, out var stamina)) + { + if (stamina.Critical) + { + _entityManager.DeleteEntity(uid); + return; + } + + if (_staminaSystem.TryTakeStamina(uid, 10, ignoreResist: true)) + return; + + } + if (drowner.DrownTime >= DrownTimeMax) + { + _entityManager.DeleteEntity(uid); + } + drowner.DrownTime += 1; + } + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/UndrowableComponent.cs b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/UndrowableComponent.cs new file mode 100644 index 00000000000..8deb8fd34d2 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/PlayerDrowning/UndrowableComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Imperial.Medieval.Ships.PlayerDrowning; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class UndrowableComponent : Component +{ + +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sail/SailSystem.cs b/Content.Server/Imperial/Medieval/Ships/Sail/SailSystem.cs new file mode 100644 index 00000000000..4d5c1c227e8 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sail/SailSystem.cs @@ -0,0 +1,174 @@ +using System.Numerics; +using Content.Server.Administration.Logs; +using Content.Server.Imperial.Medieval.Ships.Helm; +using Content.Server.Imperial.Medieval.Ships.ShipDrowning; +using Content.Server.Imperial.Medieval.Ships.WeatherVane; +using Content.Shared._RD.Weight.Components; +using Content.Shared._RD.Weight.Systems; +using Content.Shared.Changeling; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Imperial.Medieval.Ships.Sail; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Content.Shared.Imperial.Medieval.Ships.ShipDrowning; +using Content.Shared.Imperial.Medieval.Ships.Wind; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Popups; +using Robust.Shared.Configuration; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Maths; + +namespace Content.Server.Imperial.Medieval.Ships.Sail; + +/// +/// This handles... +/// +public sealed class SailSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly RDWeightSystem _rdWeight = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly HelmSystem _helm = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private TimeSpan _nextCheckTime; + + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnFold); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(_cfg.GetCVar(ShipsCCVars.WindDelay)); + if (!_cfg.GetCVar(ShipsCCVars.WindEnabled)) + return; + var ships = new List(); + var windAngle = _cfg.GetCVar(ShipsCCVars.WindRotation); + var windForce = _cfg.GetCVar(ShipsCCVars.StormLevel); + foreach (var sailComponent in EntityManager.EntityQuery()) + { + if (sailComponent.Folded) + continue; + + var sailEntity = sailComponent.Owner; + var boat = _transform.GetParentUid(sailEntity); + EnsureComp(boat); + var boatAngle = new Angle(); + var entities = _lookup.GetEntitiesIntersecting(boat); + foreach (var entity in entities) + { + if (HasComp(entity)) + boatAngle = _transform.GetWorldRotation(entity); + + } + + var sailAngle = (float)_transform.GetWorldRotation(sailEntity)*180; + + while ( Math.Abs(sailAngle) > 360) + { + if (sailAngle > 0) + sailAngle -= 360; + else + sailAngle += 360; + } + + while ( Math.Abs(sailAngle) > 360) + { + if (sailAngle > 0) + sailAngle -= 360; + else + sailAngle += 360; + } + + var diffAngle = sailAngle - windAngle; + var wind = _cfg.GetCVar(ShipsCCVars.WindPower); + if (!HasComp < SeaComponent > (_transform.GetMap(boat))) + wind = 1; + var force = windForce * MathF.Cos(diffAngle/180) * sailComponent.SailSize * wind; + + Push(sailEntity, force, boatAngle , push: sailComponent.Push, helm: sailComponent.Helm); + if (!ships.Contains(boat)) + ships.Add(boat); + } + } + } + + private void Push(EntityUid sail, float windForce, Angle torque , bool push = true, bool helm = false) + { + var boat = _transform.GetParentUid(sail); + + var boatAngle = _transform.GetWorldRotation(boat); + + var weight = _rdWeight.GetTotal(boat); + + var entities = _lookup.GetEntitiesIntersecting(boat); + + if (entities.Count > 1000000) + return; + + foreach (var entity in entities) + { + if (HasComp(entity)) + weight += _rdWeight.GetTotal(entity); + } + + if (weight == 0) + weight = 10; + torque += Angle.FromDegrees(90); + if (windForce < 0) + windForce *= (float)0.5; + var impulse = torque.ToVec() * windForce; + if (!push) + { + _transform.SetWorldRotation(sail, Angle.FromDegrees(_cfg.GetCVar(ShipsCCVars.WindRotation))); + } + + if (helm) + _helm.RotateShip(boat,sail, _helm.CheckForce(boat, sail)); + + if (_helm.CheckForce(boat, sail) > _cfg.GetCVar(ShipsCCVars.ShipsMaxSpeed)) + return; + + _physics.ApplyLinearImpulse(boat, impulse); + } + + + private void OnFold(EntityUid uid, SailComponent component, SailFoldEvent args) + { + if (args.Cancelled) + return; + + var rot = _transform.GetWorldRotation(uid); + var coords = _transform.GetMapCoordinates(uid); + Del(uid); + if (component.Folded) + Spawn("MedievalDecorShipSailReady", coords, rotation: rot); + else + { + Spawn("MedievalDecorShipSailShipup", coords, rotation: rot); + } + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sea/Generation/IslandPrototype.cs b/Content.Server/Imperial/Medieval/Ships/Sea/Generation/IslandPrototype.cs new file mode 100644 index 00000000000..131219feb30 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sea/Generation/IslandPrototype.cs @@ -0,0 +1,33 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.Imperial.Medieval.Ships.Sea.Generation; + +/// +/// Прототип острова — содержит конфигурацию для генерации. +/// Используется для определения типа, размера и других параметров. +/// +[Prototype("island")] +public sealed partial class IslandPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; + + /// + /// Размер острова в тайлах (например, 1 = 1x1, 2 = 2x2, 3 = 3x3). + /// + [DataField("size", required: true)] + public int Size { get; private set; } + + /// + /// Множитель веса для случайной генерации (если нужно балансировать частоту). + /// + [DataField("spawnWeight")] + public int SpawnWeight { get; private set; } = 1; + + /// + /// Опциональное имя для логирования или интерфейса (не используется в спавне). + /// + [DataField("name", required: false)] + public string? Name { get; private set; } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sea/Generation/SeasGenerationSystem.cs b/Content.Server/Imperial/Medieval/Ships/Sea/Generation/SeasGenerationSystem.cs new file mode 100644 index 00000000000..ed44b180c5f --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sea/Generation/SeasGenerationSystem.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Imperial.Medieval.Ships.Sea.Init; +using Content.Server.MagicBarrier.Components; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Robust.Server.GameObjects; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Server.Imperial.Medieval.Ships.Sea.Generation; + +public sealed class SeasGenerationSystem : EntitySystem +{ + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SeaMatrixInitSystem _seaMatrix = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; // 🔥 НОВЫЙ ДЕПЕНДЕНС + + private const int MapMin = -75; + private const int MapMax = 75; + + // Используем прототипы вместо строк + private static readonly (string PrototypeId, int Count)[] IslandConfig = { + ("PirateIsland", 1), // 1 большой + ("FrendlyIslands", 2), // 2 средних + ("VolcanicIsland", 10) // 10 мелких + }; + + public override void Initialize() + { + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnSeasGeneration); + } + + private void OnInit(EntityUid uid, MagicBarrierComponent component, ComponentInit args) + { + if (component.SeaMatrix == null) + component.SeaMatrix = new SeaMatrix(new List<(int x, int y)> + { + (2, 2), (2, 3), (2, 4), + (3, 2), (3, 3), (3, 4), + (4, 2), (4, 3), (4, 4), + }); + + if (component.SeaInitalazed) return; + + var seaMatrix = component.SeaMatrix; + + // Создаем 25 карт моря (5x5) + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + if (!seaMatrix.NeedsGeneration(x, y)) continue; + + var mapUid = _map.CreateMap(); + _metaData.SetEntityName(mapUid, $"Море {x} {y}"); + var mapId = _transform.GetMapId(mapUid); + AddComp(mapUid); + seaMatrix.SetSeaId(x, y, mapId); + seaMatrix.SetGenerated(x, y, true); + } + } + + // ✅ ГЕНЕРИРУЕМ ОСТРОВА С ИСПОЛЬЗОВАНИЕМ IPrototypeManager + GenerateIslandsOnSeaMaps(seaMatrix); + + component.SeaInitalazed = true; + } + + /// + /// Генерирует острова, используя IPrototypeManager и конфигурацию по ID. + /// Все острова размещаются в общем пространстве [-75, 75], без пересечений. + /// + private void GenerateIslandsOnSeaMaps(SeaMatrix seaMatrix) + { + var generatedObjects = new List(); + var occupiedTiles = new HashSet<(int X, int Y)>(); + + // Собираем все MapId карт моря + var seaMapIds = new List(); + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + var cell = seaMatrix.GetCell(x, y); + if (cell.NeedGenerate && !(cell.SeaId == new MapId(-1))) + seaMapIds.Add(cell.SeaId); + } + } + + if (seaMapIds.Count == 0) + { + Logger.Warning("No sea maps found to generate islands on!"); + return; + } + + // Проходим по конфигурации островов + foreach (var (prototypeId, count) in IslandConfig) + { + // Проверяем, существует ли прототип + if (!_prototypeManager.TryIndex(prototypeId, out var prototype)) + { + Logger.Warning($"Island prototype '{prototypeId}' not found! Skipping."); + continue; + } + + for (int i = 0; i < count; i++) + { + int attempts = 0; + const int maxAttempts = 100; + EntityUid? newObj = null; + + while (++attempts <= maxAttempts) + { + // Выбираем случайную карту моря + var targetMapId = seaMapIds[_random.Next(seaMapIds.Count)]; + + // Случайная позиция на карте + int x = _random.Next(MapMin, MapMax - prototype.Size + 1); + int y = _random.Next(MapMin, MapMax - prototype.Size + 1); + + // Проверяем пересечения + bool overlaps = false; + var newTiles = new List<(int X, int Y)>(); + + for (int dx = 0; dx < prototype.Size; dx++) + { + for (int dy = 0; dy < prototype.Size; dy++) + { + var tile = (x + dx, y + dy); + if (occupiedTiles.Contains(tile)) + { + overlaps = true; + break; + } + newTiles.Add(tile); + } + if (overlaps) + break; + } + + if (!overlaps) + { + // ✅ СПАВН С ПРОТОТИПОМ, а не строкой! + newObj = EntityManager.SpawnEntity(prototypeId, new MapCoordinates(x, y, targetMapId)); + // _mapLoader.TryLoadGrid(targetMapId, ); + if (newObj.HasValue) + { + generatedObjects.Add(newObj.Value); + foreach (var tile in newTiles) + occupiedTiles.Add(tile); + break; // Успешно + } + } + } + + if (attempts > maxAttempts) + { + Logger.Warning($"Failed to generate {prototypeId} after {maxAttempts} attempts."); + } + } + } + + Logger.Info($"Successfully generated {generatedObjects.Count} islands across {seaMapIds.Count} sea maps."); + } + + public sealed class SeasGenerationEvent + { + public MapId MapId { get; set; } + public int Count { get; set; } + public string Prototype { get; set; } = "Reef"; + } + + private void OnSeasGeneration(SeasGenerationEvent ev) + { + // Оставлено для будущего расширения + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sea/Init/MapForSeasInitComponent.cs b/Content.Server/Imperial/Medieval/Ships/Sea/Init/MapForSeasInitComponent.cs new file mode 100644 index 00000000000..0a8a04af659 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sea/Init/MapForSeasInitComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Imperial.Medieval.Ships.Sea.Init; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class MapForSeasInitComponent : Component +{ + [DataField("mapX")] + public int MapX { get; set; } + [DataField("mapY")] + public int MapY { get; set; } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sea/Init/NotSeaComponent.cs b/Content.Server/Imperial/Medieval/Ships/Sea/Init/NotSeaComponent.cs new file mode 100644 index 00000000000..dc45733eb13 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sea/Init/NotSeaComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Imperial.Medieval.Ships.Sea.Init; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class NotSeaComponent : Component +{ + [DataField("NotSeaPosX")] + public int NotSeaPosX; + + [DataField("NotSeaPosY")] + public int NotSeaPosY; +} diff --git a/Content.Server/Imperial/Medieval/Ships/Sea/Init/SeaMatrixInitSystem.cs b/Content.Server/Imperial/Medieval/Ships/Sea/Init/SeaMatrixInitSystem.cs new file mode 100644 index 00000000000..ea6a20cc3c4 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Sea/Init/SeaMatrixInitSystem.cs @@ -0,0 +1,171 @@ +using Content.Server.MagicBarrier.Components; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Content.Shared.Interaction; +using Robust.Server.GameObjects; +using Robust.Shared.Map; + +namespace Content.Server.Imperial.Medieval.Ships.Sea.Init; + +/// +/// по идее должно работать с матрицой моря +/// грёбаные костыли, я только примерно понимаю что делаю но вроде как всё нормально(нет я не пишу нейронкой, просто не особо понимаю, райдер помогает исправлять слишком грубые ошибки) +/// +public sealed class SeaMatrixInitSystem : EntitySystem +{ + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityManager _entManager = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnInitMap); + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInitMap(EntityUid uid, MapForSeasInitComponent component, ComponentInit args) + { + if (component.MapX == 0 && component.MapY == 0) + return; + SetMapPos(uid, component.MapX, component.MapY); + } + + + private void OnActivate(EntityUid uid, MapForSeasInitComponent component, ActivateInWorldEvent args) + { + SetMapPos(args.Target, component.MapX, component.MapY); + } + + private void OnInteractUsing(EntityUid uid, MapForSeasInitComponent component, InteractUsingEvent args) + { + SetMapPos(args.Target, component.MapX, component.MapY); + } + + public void SetMapPos(EntityUid uid, int x, int y) + { + var mapId = _transform.GetMapId(uid); + foreach (var magicBarrier in EntityManager.EntityQuery()) + { + if (magicBarrier.SeaMatrix is null) + continue; + magicBarrier.SeaMatrix.SetSeaId(x,y,mapId); + } + } + /// + /// ищем мапу либо -1 пишем + /// + public MapId TryFoundMap(int x, int y) + { + if (_entManager == null) + throw new InvalidOperationException("EntityManager not initialized!"); + foreach (var notSeaComponent in _entManager.EntityQuery()) + { + if (notSeaComponent.NotSeaPosX != x || notSeaComponent.NotSeaPosY != y) + continue; + + return _transform.GetMapId(notSeaComponent.Owner); + } + return new MapId(-1); + } + + + + + + +} +public sealed class SeaMatrix +{ + + private readonly SeaCell[,] _matrix = new SeaCell[5, 5]; // 5x5 матрица + + + public SeaMatrix(IEnumerable<(int x, int y)>? nonGeneratableCoordinates = null) + { + + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + _matrix[x, y] = new SeaCell + { + SeaId = new MapId(-1), + NeedGenerate = true + }; + } + } + + + if (nonGeneratableCoordinates != null) + { + foreach (var (x, y) in nonGeneratableCoordinates) + { + if (x >= 0 && x < 5 && y >= 0 && y < 5) + { + _matrix[x, y].NeedGenerate = false; + } + } + } + } + + /// + /// Получение ячейки по координатам + /// + public SeaCell GetCell(int x, int y) + { + if (x < 0 || x >= 5 || y < 0 || y >= 5) + throw new ArgumentOutOfRangeException("Coordinates out of 5x5 range!"); + + return _matrix[x, y]; + } + /// + /// Установка ID моря + /// + public void SetSeaId(int x, int y, MapId seaId) + { + if (x < 0 || x >= 5 || y < 0 || y >= 5) + throw new ArgumentOutOfRangeException("Coordinates out of 5x5 range!"); + + _matrix[x, y].SeaId = seaId; + _matrix[x, y].NeedGenerate = false; // Сбрасываем флаг после назначения ID + } + + /// + /// Проверка, нужно ли генерировать море в ячейке + /// + public bool NeedsGeneration(int x, int y) + { + return GetCell(x, y).NeedGenerate; + } + /// + /// Ставим значение ячейке + /// + public void SetGenerated(int x, int y, bool generated) + { + _matrix[x, y].NeedGenerate = generated; + } + + /// + /// Поиск ячейки по айди + /// + public (int, int)? FoundSell(MapId seaId, SeaMatrix seaMatrix) + { + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + if (_matrix[x, y].SeaId == seaId) + { + return (x, y); + } + } + } + return null; + } +} +public struct SeaCell +{ + public MapId SeaId; // Уникальный ID моря + public bool NeedGenerate; // Флаг, что море нужно сгенерировать +} diff --git a/Content.Server/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningSystem.cs b/Content.Server/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningSystem.cs new file mode 100644 index 00000000000..7afc40dc8f5 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningSystem.cs @@ -0,0 +1,86 @@ +using System.Linq; +using Content.Server.Administration.Commands; +using Content.Server.Imperial.Medieval.Ships.PlayerDrowning; +using Content.Server.Imperial.Medieval.Ships.Wave; +using Content.Shared.Damage; +using Content.Shared.Imperial.Medieval.Ships.ShipDrowning; +using Robust.Shared.Map.Components; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.ShipDrowning; + +/// +/// This handles... +/// +public sealed class ShipDrowningSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly WaveSystem _wave = default!; + + private const float DefaultReloadTimeSeconds = 10f; + + private TimeSpan _nextCheckTime; + /// + public override void Initialize() + { + base.Initialize(); + _nextCheckTime = _timing.CurTime + TimeSpan.FromSeconds(DefaultReloadTimeSeconds); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(DefaultReloadTimeSeconds); + + foreach (var component in EntityManager.EntityQuery()) + { + var ship = component.Owner; + + if (component.DrownLevel > component.DrownMaxLevel) + { + if (component.DrownLevel > component.DrownMaxLevel * 10) + { + _entityManager.DeleteEntity(ship); + return; + } + EnsureComp(ship); + return; + } + if (HasComp(ship)) + RemComp(ship); + + if (!TryComp(ship, out var mapGrid)) + return; + var allTilesEnumerator = _map.GetAllTilesEnumerator(ship, mapGrid); + + var brokenTilesCount = 0; + var allTilesCount = 0; + var brokenlevel = 0; + + while (allTilesEnumerator.MoveNext(out var tile)) + { + allTilesCount++; + brokenlevel = 0; + foreach (var stage in _wave.Stages) + { + if (stage.Item2 == tile.Value.Tile.TypeId) + brokenTilesCount+= brokenlevel; + brokenlevel++; + } + } + + component.DrownLevel += brokenTilesCount; + component.DrownMaxLevel = allTilesCount * 100; + } + + } + + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/ShipTeleporting/ShipTeleportSystem.cs b/Content.Server/Imperial/Medieval/Ships/ShipTeleporting/ShipTeleportSystem.cs new file mode 100644 index 00000000000..e441841746c --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/ShipTeleporting/ShipTeleportSystem.cs @@ -0,0 +1,129 @@ +using Content.Server.Administration.Logs; +using Content.Server.Imperial.Medieval.Ships.Wave; +using Content.Server.MagicBarrier.Components; +using Content.Shared.Database; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Content.Shared.Imperial.Medieval.Ships.ShipDrowning; +using Robust.Shared.Configuration; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.ShipTeleporting; + +/// +/// This handles... +/// +public sealed class ShipTeleportSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly WaveSystem _wave = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + + private TimeSpan _nextCheckTime; + /// + public override void Initialize() + { + + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(_cfg.GetCVar(ShipsCCVars.WaveDelay)); + + foreach (var seaComponent in EntityManager.EntityQuery()) + { + + var sea = seaComponent.Owner; + + var entities = new HashSet>(); + _lookup.GetEntitiesOnMap(_transform.GetMapId(sea), entities); + foreach (var shipComp in entities) + { + var ship = shipComp.Owner; + + + var coords = _transform.GetMapCoordinates(ship); + if (Math.Abs(coords.X) > 250 || Math.Abs(coords.Y) > 250) + { + TeleportShip(ship, coords); + } + } + } + } + } + + private void TeleportShip(EntityUid ship, MapCoordinates coords) + { + var mapScale = _cfg.GetCVar(ShipsCCVars.MapScale); + var tpRange = _cfg.GetCVar(ShipsCCVars.TeleportRange); + var tpDist = mapScale + tpRange; + var newcoords = coords.Position; + foreach (var magicBarrier in EntityManager.EntityQuery()) + { + var seematrix = magicBarrier.SeaMatrix; + if ( seematrix is null) + continue; + var seamap = seematrix.FoundSell(coords.MapId, seematrix); + if (seamap is null) + continue; + + var (x, y) = seamap.Value; + + if (Math.Abs(newcoords.X) > tpDist) + { + if (newcoords.X > 0) + { + x += 1; + newcoords.X = -tpDist; + } + else + { + x -= 1; + newcoords.X = tpDist; + } + } + + if (Math.Abs(newcoords.Y) > tpDist) + { + if (newcoords.Y > 0) + { + y += 1; + newcoords.Y = -tpDist; + } + + else + { + y -= 1; + newcoords.Y = tpDist; + } + } + + var mapId = seematrix.GetCell(x,y).SeaId; + if (mapId == new MapId(-1)) + { + EnsureComp(ship, out var comp); + comp.DrownLevel += (int)Math.Abs(coords.Position.X) + (int)Math.Abs(coords.Position.Y); + } + + var nmapcoords = new MapCoordinates(newcoords, mapId); + + _transform.SetMapCoordinates(ship, nmapcoords); + break; + } + + + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Storm/StormSystem.cs b/Content.Server/Imperial/Medieval/Ships/Storm/StormSystem.cs new file mode 100644 index 00000000000..935021994a6 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Storm/StormSystem.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Imperial.Medieval.Ships.Storm; + +/// +/// This handles... +/// +public sealed class StormSystem : EntitySystem +{ + /// + public override void Initialize() + { + + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipComponent.cs b/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipComponent.cs new file mode 100644 index 00000000000..3c384b578c3 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Imperial.Medieval.Ships.SummonShip; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class SummonShipComponent : Component +{ + [DataField(required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string File = "/Ships/Victoria.yml"; + + [DataField] + public float Delay = 2; +} diff --git a/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipSystem.cs b/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipSystem.cs new file mode 100644 index 00000000000..4ffffda320d --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/SummonShip/SummonShipSystem.cs @@ -0,0 +1,63 @@ +using System.IO; +using Content.Server.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Interaction.Events; +using Content.Shared.Timing; +using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Map; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server.Imperial.Medieval.Ships.SummonShip; + +/// +/// This handles... +/// +public sealed class SummonShipSystem : EntitySystem +{ + + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly IMapManager _mapMan = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly UseDelaySystem _delay = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUse); + } + + private void OnUse(EntityUid uid, SummonShipComponent comp, UseInHandEvent args) + { + if (args.Handled) + return; + args.Handled = true; + var time = TimeSpan.FromSeconds(comp.Delay); + _delay.SetLength(uid, time); + Timer.Spawn(time, () => Use(uid, comp)); + } + + private void Use(EntityUid uid, SummonShipComponent comp) + { + var mapId = _transform.GetMapId(uid); + var worldPos = _transform.GetWorldPosition(uid); + + var path = new ResPath(comp.File); + + if (!_mapLoader.TryLoadGrid(mapId, path, out var grid, offset: worldPos)) + { + _adminLog.Add(LogType.Action, LogImpact.High, $"Ошибка Загрузки грида из {path} сущность загрузки {uid}"); + return; + } + + EntityManager.QueueDeleteEntity(uid); + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveComponent.cs b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveComponent.cs new file mode 100644 index 00000000000..14f7c13109e --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.Imperial.Medieval.Ships.Wave.Spawn; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class SpawnWaveComponent : Component +{ + [DataField] + public bool DeleteOnCollide = true; +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveSystem.cs b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveSystem.cs new file mode 100644 index 00000000000..bb6fa428403 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWaveSystem.cs @@ -0,0 +1,38 @@ +using Content.Shared.Damage; +using Robust.Server.GameObjects; +using Robust.Shared.Map; + +namespace Content.Server.Imperial.Medieval.Ships.Wave.Spawn; + +/// +/// Спавнит волны, щиииииткод +/// +public sealed class SpawnWaveSystem : EntitySystem +{ + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + /// + public override void Initialize() + { + SubscribeLocalEvent(OnInit); + } + + private void OnInit(EntityUid uid, SpawnWaveComponent component, ComponentInit args) + { + var entcoords = _transform.GetMoverCoordinates(uid); + var mapcoords = _transform.GetMapCoordinates(uid); + var grid = _mapManager.CreateGridEntity(mapcoords.MapId); + var waveComponent = EnsureComp(grid); + waveComponent.DeleteOnCollide = component.DeleteOnCollide; + _tileDefinitionManager.TryGetDefinition("FloorWood", out var tileDefinition);// сюда поставить воду + if (tileDefinition == null) + return; + _map.SetTile(grid, new Vector2i(0,0),new Tile(tileDefinition.TileId, 0, 0));// создаёт тайлик воды надо поставить воду вон туда + if (HasComp(grid)) + _transform.SetCoordinates(grid, entcoords); + _entityManager.DeleteEntity(uid); + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWindWaveSystem.cs b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWindWaveSystem.cs new file mode 100644 index 00000000000..a256a6ee574 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wave/Spawn/SpawnWindWaveSystem.cs @@ -0,0 +1,99 @@ +using System.Numerics; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Content.Shared.Imperial.Medieval.Ships.ShipDrowning; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Configuration; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.Wave.Spawn; + +/// +/// Призыв волн раз в какое то время +/// +public sealed class SpawnWindWaveSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly WaveSystem _wave = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private TimeSpan _nextCheckTime; + /// + public override void Initialize() + { + + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(_cfg.GetCVar(ShipsCCVars.WaveDelay)); + + foreach (var seaComponent in EntityManager.EntityQuery()) + { + if (seaComponent.Disabled) + continue; + var sea = seaComponent.Owner; + var seaMapId = _transform.GetMapId(sea); + var ships = new HashSet>(); + _lookup.GetEntitiesOnMap(seaMapId, ships); + foreach (var shipcomp in ships) + { + var ship = shipcomp.Owner; + + var waveCount = _random.Next(0, (int)_cfg.GetCVar(ShipsCCVars.StormLevel)); + var waveCoords = new EntityCoordinates(ship, GenerateWave()); + Vector2 force; + for (var i = 0; i < waveCount; i++) + { + waveCoords = new EntityCoordinates(ship, GenerateWave()); + force = waveCoords.Position.Normalized()*_cfg.GetCVar(ShipsCCVars.WaveForce)*-1; + _wave.SpawnWave(waveCoords,seaMapId, force); + } + + } + + + } + + } + } + private Vector2 GenerateWave(float radius = 0, float targetAngle = 0, float halfAngle = 3.0235f) + { + if (halfAngle == 3.0235f) // я прифигею если вы рандомно сможете получить это число + halfAngle = _cfg.GetCVar(ShipsCCVars.WaveSpawnAngle)*_cfg.GetCVar(ShipsCCVars.StormLevel); + + halfAngle /= 180; + + if (targetAngle == 0) + targetAngle = _cfg.GetCVar(ShipsCCVars.WindRotation); + if (radius == 0) + radius = _cfg.GetCVar(ShipsCCVars.WaveSpawnRange); + + var u = _random.NextDouble(); + var v = _random.NextDouble(); + + var rho = radius * Math.Sqrt(u); + + var phiMin = targetAngle - halfAngle; + var phiMax = targetAngle + halfAngle; + var phi = phiMin + (phiMax - phiMin) * v; + + + var x = (float)(rho * Math.Cos(phi)); + var y = (float)(rho * Math.Sin(phi)); + + return new Vector2(x*100, y); + } + +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wave/WaveComponent.cs b/Content.Server/Imperial/Medieval/Ships/Wave/WaveComponent.cs new file mode 100644 index 00000000000..fbf46d35f98 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wave/WaveComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.Damage; + +namespace Content.Server.Imperial.Medieval.Ships.Wave; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class WaveComponent : Component +{ + /// + /// Damage specifier that is multiplied against the calculated damage amount to determine what damage is applied to the colliding entity. + /// + /// + /// The values of this should add up to 1 or else the damage will be scaled. + /// + [DataField] + public DamageSpecifier DamageTypes = new(); + + /// + /// A list of entities that this meteor has collided with. used to ensure no double collisions occur. + /// + [DataField] + public HashSet HitList = new(); + + [DataField] + public float Strength = 1; + [DataField] + public float Direction = 1; + + [DataField] + public bool DeleteOnCollide = true; +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wave/WaveSystem.cs b/Content.Server/Imperial/Medieval/Ships/Wave/WaveSystem.cs new file mode 100644 index 00000000000..99df4fa16e8 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wave/WaveSystem.cs @@ -0,0 +1,208 @@ +using System.Linq; +using System.Numerics; +using Content.Server.Administration.Logs; +using Content.Server.Destructible; +using Content.Shared.Construction.Conditions; +using Content.Shared.Damage; +using Content.Shared.Database; +using Content.Shared.Imperial.Medieval.Additions; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Maps; +using Content.Shared.Mobs.Systems; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Content.Shared.Tag; +using Content.Shared.Tiles; +using Content.Shared.Trigger.Components; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Events; +using Robust.Shared.Player; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Timing; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Imperial.Medieval.Ships.Wave; + +public sealed class WaveSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly DestructibleSystem _destructible = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly FloorTileSystem _floorTileSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly TileSystem _tile = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IAdminLogManager _adminlogs = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly TagSystem _tags = default!; + + + private readonly Random _random = new(); + public (string, ushort)[] Stages = + { + ("FloorWood", (ushort)1), + ("FloorSteel", (ushort)2), + ("Plating", (ushort)3), + ("FloorWhite", (ushort)4) + }; + private bool _initialized; + + public override void Initialize() + { + SubscribeLocalEvent(OnCollide); + + } + private void Startup() + { + if (_initialized) + return; + if (Stages == null || Stages.Length == 0) + return; + + for (int i = 0; i < Stages.Length; i++) + { + var stage = Stages[i]; + if (!_tileDefinitionManager.TryGetDefinition(stage.Item1, out var tileDefinition)) + continue; + + Stages[i] = (stage.Item1, tileDefinition.TileId); + } + _initialized = true; + } + + private void OnCollide(EntityUid uid, WaveComponent component, ref StartCollideEvent args) + { + if (!_initialized) + Startup(); + if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.OtherEntity)) + return; + if (component.HitList.Contains(args.OtherEntity)) + return; + if (_cfg.GetCVar(ShipsCCVars.WaveMinToBreakLevel) > _cfg.GetCVar(ShipsCCVars.StormLevel)) + _entityManager.DeleteEntity(args.OurEntity); + EnsureComp(args.OurEntity); + var collisionPos = _transform.GetMapCoordinates(args.OurEntity); + var gridEntity = args.OtherEntity; + if (!_entityManager.TryGetComponent(gridEntity, out var mapGridComp)) + return; + + var grid = new Entity(gridEntity, mapGridComp); + + var tileRef = _map.GetTileRef(grid, collisionPos); + + var centerTilePos = _map.MapToGrid(grid, collisionPos); + + var radiusTiles = _cfg.GetCVar(ShipsCCVars.WaveRadiusTiles) + _cfg.GetCVar(ShipsCCVars.StormLevel); + + var antiradius = (int)radiusTiles*-1; + + var nearbyTiles = new List(); + + + for (int dx = antiradius; dx <= radiusTiles; dx++) + { + for (int dy = antiradius; dy <= radiusTiles; dy++) + { + var tilePos = centerTilePos + new EntityCoordinates(gridEntity, new Vector2(dx, dy)) ; + var tile = _map.GetTileRef(grid, tilePos); + + if (tile.Tile.IsEmpty) + continue; + var wallcheck = new HashSet(); + _lookup.GetEntitiesInTile(tile, wallcheck); + var stop = false; + foreach (var wall in wallcheck) + { + if (_tags.HasTag(wall, "Wall")) + { + stop = true; + } + + } + if (stop) + continue; + + var distance = Vector2.Distance(centerTilePos.Position, tilePos.Position); + if (distance <= radiusTiles) + nearbyTiles.Add(((int)tilePos.X, (int)tilePos.Y)); + } + } + + _random.Shuffle(nearbyTiles); + + int tilesToReplace = Math.Min(_random.Next(0,_cfg.GetCVar(ShipsCCVars.WaveMaxBreakCount)), nearbyTiles.Count); + for (int i = 0; i < tilesToReplace; i++) + { + var tilePos = nearbyTiles[i]; + if (!_map.TryGetTile(grid, tilePos, out var tile)) + continue; + var stagelast = Stages.Length-1; + + if (tile.TypeId == Stages[stagelast].Item2 || tile.IsEmpty) + continue; + var index = 0; + foreach (var stage in Stages) + { + if (stage.Item2 == tile.TypeId) + break; + index++; + } + if (index == stagelast+1) + index = 0; + _map.SetTile(grid.Owner, grid , tilePos, new Tile(Stages[index+1].Item2, 0, 0)); + } + if (!TerminatingOrDeleted(args.OtherEntity)) + component.HitList.Add(args.OtherEntity); + if (component.DeleteOnCollide) + _entityManager.DeleteEntity(args.OurEntity); + } + /// + /// призывает грид на куазанных координатах относительно какой то сущности и даёт ей силу + /// coords кординаты это сущность от которой считать и вектор смещения + /// mapId айди мапы + /// force вектор силы который мы прикладываем если надо + /// deleteOnCollide при столкновении удаляем + /// lifetime = 0 не будет удалять сущность по истечению таймера + /// + public void SpawnWave(EntityCoordinates coords, MapId mapId, Vector2 force = new Vector2(), bool deleteOnCollide = true, float lifetime = 60) + { + if (!_map.TryGetMap(mapId, out var mapEntity)) + return; + + var grid = _mapManager.CreateGridEntity(mapId); + _transform.SetParent(grid, mapEntity.Value); + var waveComponent = EnsureComp(grid); + waveComponent.DeleteOnCollide = deleteOnCollide; + _tileDefinitionManager.TryGetDefinition("FloorWood", out var tileDefinition);// сюда поставить воду + if (tileDefinition == null) + return; + _map.SetTile(grid, new Vector2i(0,0), new Tile(tileDefinition.TileId, 0, 0));// создаёт тайлик воды надо поставить воду вон туда + if (HasComp(grid)) + { + _transform.SetCoordinates(grid, coords); + _physics.WakeBody(grid); + _physics.ApplyLinearImpulse(grid, force); + if (lifetime > 0) + { + var despawnComponent = EnsureComp(grid); + despawnComponent.Lifetime = lifetime; + despawnComponent.OriginalLifeTime = lifetime; + } + } + } +} diff --git a/Content.Server/Imperial/Medieval/Ships/WeatherVane/WeatherVaneComponent.cs b/Content.Server/Imperial/Medieval/Ships/WeatherVane/WeatherVaneComponent.cs new file mode 100644 index 00000000000..f7fed230b40 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/WeatherVane/WeatherVaneComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Imperial.Medieval.Ships.WeatherVane; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class WeatherVaneComponent : Component +{ + +} diff --git a/Content.Server/Imperial/Medieval/Ships/Wind/ServerWindSystem.cs b/Content.Server/Imperial/Medieval/Ships/Wind/ServerWindSystem.cs new file mode 100644 index 00000000000..9a6c7027153 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Ships/Wind/ServerWindSystem.cs @@ -0,0 +1,85 @@ +using Content.Server.Shuttles.Components; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Imperial.Medieval.Ships.Sea; +using Robust.Shared.Configuration; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Imperial.Medieval.Ships.Wind; + +/// +/// This handles... +/// +public sealed class ServerWindSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + + private TimeSpan _nextCheckTime; + /// + public override void Initialize() + { + + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + if (curTime > _nextCheckTime) + { + _nextCheckTime = curTime + TimeSpan.FromSeconds(_cfg.GetCVar(ShipsCCVars.WindChangeTime)); + RandomiseVind(); + } + } + + + private void RandomiseVind() + { + // ветерок сила + var windForce = _cfg.GetCVar(ShipsCCVars.StormLevel); + var countShips = FindShips(); + + if (windForce <= 0+countShips) + windForce += _random.Next(0, 2); + else if (windForce >= 2 + countShips || countShips >= 10) + windForce -= _random.Next(0, 2); + else + windForce += _random.Next(-1, 2); + _cfg.SetCVar(ShipsCCVars.WindPower, windForce); + + // ветерок направление + var windAngle = _cfg.GetCVar(ShipsCCVars.WindRotation); + windAngle += _random.Next(-1, 2)*5; // ±5 градусов за шаг + if (Math.Abs(windAngle) > 360) + windAngle = 0; + else if (windAngle < 0) + windAngle += 360; + + _cfg.SetCVar(ShipsCCVars.WindRotation, windAngle); + } + + private int FindShips() + { + var count = 0; + foreach (var seaComp in EntityManager.EntityQuery()) + { + if (seaComp.Disabled) + continue; + var mapUid = seaComp.Owner; + MapId mapId = new MapId(mapUid.Id); + + var ships = new HashSet>(); + _lookup.GetEntitiesOnMap(mapId, ships); + count += ships.Count; + + } + return count; + } +} diff --git a/Content.Shared/Imperial/Medieval/Administration/Ships/ShipsCCVars.cs b/Content.Shared/Imperial/Medieval/Administration/Ships/ShipsCCVars.cs new file mode 100644 index 00000000000..4c9030c5ade --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Administration/Ships/ShipsCCVars.cs @@ -0,0 +1,93 @@ +using Robust.Shared; +using Robust.Shared.Configuration; +namespace Content.Shared.Imperial.Medieval.Administration.Ships; + +/// +/// Цвары для корабликов, тут лежит ветер максимальная скорость кораблей и подобное +/// +[CVarDefs] +public sealed class ShipsCCVars : CVars +{ + /// + /// максимальная скорость + /// + public static readonly CVarDef ShipsMaxSpeed = + CVarDef.Create("ships.maxspeed", 20f, CVar.REPLICATED | CVar.SERVER); + /// + /// как часто меняется ветер + /// + public static readonly CVarDef WindChangeTime = + CVarDef.Create("ships.windchangetime", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// как часто ветер дует + /// + public static readonly CVarDef WindDelay = + CVarDef.Create("ships.winddelay", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// как часто появляются волны + /// + public static readonly CVarDef WaveDelay = + CVarDef.Create("ships.wavedelay", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// Минимальный уровень для поломки лодки (Шторма если кто не понял) + /// + public static readonly CVarDef WindEnabled = + CVarDef.Create("ships.waveenabled", true, CVar.REPLICATED | CVar.SERVER); + /// + /// сила с которой ветер толкает + /// + public static readonly CVarDef WindPower = + CVarDef.Create("ships.windpower", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// угол поворота ветра + /// + public static readonly CVarDef WindRotation = + CVarDef.Create("ships.windrotation", 0f, CVar.REPLICATED | CVar.SERVER); + /// + /// уровень шторма + /// + public static readonly CVarDef StormLevel = + CVarDef.Create("ships.stormlevel", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// скорость с которой волна спавнится + /// + public static readonly CVarDef WaveForce = + CVarDef.Create("ships.waveforce", 1f, CVar.REPLICATED | CVar.SERVER); + /// + /// радиус спавна волн + /// + public static readonly CVarDef WaveSpawnRange = + CVarDef.Create("ships.wavespawnrange", 40f, CVar.REPLICATED | CVar.SERVER); + /// + /// угол разброса волн + /// + public static readonly CVarDef WaveSpawnAngle = + CVarDef.Create("ships.wavespawnangle", 10f, CVar.REPLICATED | CVar.SERVER); + /// + /// в каком радиусе ломает волна + /// + public static readonly CVarDef WaveRadiusTiles = + CVarDef.Create("ships.waveradiustiles", 3f, CVar.REPLICATED | CVar.SERVER); + /// + /// какое максимальное количество тайлов может сломать волна + /// + public static readonly CVarDef WaveMaxBreakCount = + CVarDef.Create("ships.wavemaxbreakcount", 3, CVar.REPLICATED | CVar.SERVER); + /// + /// Минимальный уровень для поломки лодки (Шторма если кто не понял) + /// + public static readonly CVarDef WaveMinToBreakLevel = + CVarDef.Create("ships.wavemintobreaklevel", 2, CVar.REPLICATED | CVar.SERVER); + /// + /// размер карты + /// + public static readonly CVarDef MapScale = + CVarDef.Create("ships.mapscale", 100, CVar.REPLICATED | CVar.SERVER); + /// + /// радиус с которого телепортирует корабль + /// + public static readonly CVarDef TeleportRange = + CVarDef.Create("ships.teleportrange", 50, CVar.REPLICATED | CVar.SERVER); + +} + diff --git a/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorComponent.cs b/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorComponent.cs new file mode 100644 index 00000000000..42503d067c1 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Imperial.Medieval.Ships.Anchor; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class MedievalAnchorComponent : Component +{ + [DataField("Enabled")] + public bool Enabled; +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorSystem.cs b/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorSystem.cs new file mode 100644 index 00000000000..cec5bc2a831 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Anchor/MedievalAnchorSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Imperial.Medieval.Ships.Sail; +using Content.Shared.Interaction; +using Content.Shared.Construction.Components; +using Content.Shared.DoAfter; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Popups; + + +namespace Content.Shared.Imperial.Medieval.Ships.Anchor; + +/// +/// система для поднятия и опускания якоря +/// +public sealed class MedievalAnchorSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, MedievalAnchorComponent component, InteractUsingEvent args) + { + Use(args.User, args.Target); + } + + private void OnActivate(EntityUid uid, MedievalAnchorComponent component, ActivateInWorldEvent args) + { + Use(args.User, args.Target); + } + + private void Use(EntityUid playerEntity, EntityUid target) + { + + + var time = 7 -_skills.GetSkillLevel(playerEntity, "Strength") * 0.3f; + var sdoAfter = new DoAfterArgs(EntityManager, + playerEntity, + time, + new UseAnchorEvent(), + target, + target: playerEntity) + { + MovementThreshold = 0.5f, + BreakOnMove = true, + CancelDuplicate = true, + DistanceThreshold = 2, + BreakOnDamage = true, + RequireCanInteract = false, + BreakOnDropItem = true, + BreakOnHandChange = true, + NeedHand = true, + }; + + + _doAfter.TryStartDoAfter(sdoAfter); + + } +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Anchor/UseAnchorEvent.cs b/Content.Shared/Imperial/Medieval/Ships/Anchor/UseAnchorEvent.cs new file mode 100644 index 00000000000..e6fa7120542 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Anchor/UseAnchorEvent.cs @@ -0,0 +1,17 @@ +using Content.Shared.DoAfter; + +using Robust.Shared.Serialization; + + +namespace Content.Shared.Imperial.Medieval.Ships.Anchor; + +/// +/// Ивент для поднятия якоря +/// +[Serializable, NetSerializable] +public sealed partial class UseAnchorEvent : SimpleDoAfterEvent +{ +} + + + diff --git a/Content.Shared/Imperial/Medieval/Ships/Oar/OarComponent.cs b/Content.Shared/Imperial/Medieval/Ships/Oar/OarComponent.cs new file mode 100644 index 00000000000..7574ed06be6 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Oar/OarComponent.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +namespace Content.Shared.Imperial.Medieval.Ships.Oar; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class OarComponent : Component +{ + [DataField] + public int Power = 200; + + [DataField] + public int SpeedModifier = 1; + + [DataField] + public Angle Direction; +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Oar/OarEvent.cs b/Content.Shared/Imperial/Medieval/Ships/Oar/OarEvent.cs new file mode 100644 index 00000000000..581fdef3718 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Oar/OarEvent.cs @@ -0,0 +1,12 @@ + +using Content.Shared.DoAfter; + +using Robust.Shared.Serialization; + +namespace Content.Shared.Imperial.Medieval.Ships.Oar; + + +[Serializable, NetSerializable] +public sealed partial class OnOarDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Oar/OarSystem.cs b/Content.Shared/Imperial/Medieval/Ships/Oar/OarSystem.cs new file mode 100644 index 00000000000..94fb5babda2 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Oar/OarSystem.cs @@ -0,0 +1,90 @@ +using System.Numerics; +using Content.Shared._RD.Weight.Components; +using Content.Shared._RD.Weight.Systems; +using Content.Shared.Coordinates; +using Content.Shared.DoAfter; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; + + +using Content.Shared.Database; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction.Components; +using Content.Shared.Movement.Components; +using Robust.Shared.Map; + + +namespace Content.Shared.Imperial.Medieval.Ships.Oar; + +/// +/// По идее это должно быть тут, но млять грёбаный делей +/// +public sealed class OarSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly RDWeightSystem _rdWeight = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnOarAfterInteract); + } + + private void OnOarAfterInteract(EntityUid uid, OarComponent component, AfterInteractEvent args) + { + var playerEntity = args.User; + + if (args.Handled || !args.CanReach ) + return; + + var boat = _transform.GetParentUid(playerEntity); + + if (boat == args.ClickLocation.EntityId) + return; + + var clickEntity = args.ClickLocation.EntityId; + if (boat == _transform.GetParentUid(clickEntity)) + return; + + var time = 7 -_skills.GetSkillLevel(playerEntity, "Agility") * 0.3f; + var sdoAfter = new DoAfterArgs(EntityManager, + playerEntity, + time, + new OnOarDoAfterEvent(), + args.Used, + args.Target, + args.Used) + { + MovementThreshold = 0.1f, + BreakOnMove = true, + CancelDuplicate = true, + DistanceThreshold = 2, + BreakOnDamage = true, + RequireCanInteract = false, + BreakOnDropItem = true, + BreakOnHandChange = true, + NeedHand = true, + }; + _doAfter.TryStartDoAfter(sdoAfter); + var playerPosition = _transform.GetWorldPosition(playerEntity); + var boatPosition = _transform.ToWorldPosition(args.ClickLocation); + var direction = (playerPosition - boatPosition).ToAngle(); + component.Direction = direction; + + _popup.PopupClient($"Ты гребёшь в сторону взгляда", playerEntity); + EnsureComp(playerEntity); + EnsureComp(playerEntity); + + } +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Sail/SailComponent.cs b/Content.Shared/Imperial/Medieval/Ships/Sail/SailComponent.cs new file mode 100644 index 00000000000..4c5331dee8a --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Sail/SailComponent.cs @@ -0,0 +1,20 @@ +namespace Content.Shared.Imperial.Medieval.Ships.Sail; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class SailComponent : Component +{ + [DataField("SailSize")] + public int SailSize = 1; + + [DataField("Folded")] + public bool Folded; + + [DataField("Helm")] + public bool Helm; + + [DataField("Push")] + public bool Push = true; +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Sail/SailUseEvent.cs b/Content.Shared/Imperial/Medieval/Ships/Sail/SailUseEvent.cs new file mode 100644 index 00000000000..783ba2e69e9 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Sail/SailUseEvent.cs @@ -0,0 +1,15 @@ +using Content.Shared.DoAfter; + +using Robust.Shared.Serialization; + +namespace Content.Shared.Imperial.Medieval.Ships.Sail; + + +[Serializable, NetSerializable] +public sealed partial class SailUseEvent : SimpleDoAfterEvent +{ +} + +public sealed partial class SailFoldEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Sail/SharedSailSystem.cs b/Content.Shared/Imperial/Medieval/Ships/Sail/SharedSailSystem.cs new file mode 100644 index 00000000000..de85335a9d6 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Sail/SharedSailSystem.cs @@ -0,0 +1,122 @@ +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Ghost; +using Content.Shared.Imperial.Medieval.Administration.Ships; +using Content.Shared.Imperial.Medieval.Follower; +using Content.Shared.Imperial.Medieval.Ships.Oar; +using Content.Shared.Imperial.Medieval.Skills; +using Content.Shared.Interaction; +using Content.Shared.Verbs; +using Robust.Shared.Configuration; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Shared.Imperial.Medieval.Ships.Sail; + +/// +/// вращяет парус по ветру +/// +public sealed class SharedSailSystem : EntitySystem +{ + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly SharedSkillsSystem _skills = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + /// + public override void Initialize() + { + SubscribeLocalEvent>(OnGetAlternativeVerbs); + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnUse); + } + + private void OnActivate(EntityUid uid, SailComponent component, ActivateInWorldEvent args) + { + UseDelay(args.User, args.Target); + } + + private void OnInteractUsing(EntityUid uid, SailComponent component, InteractUsingEvent args) + { + UseDelay(args.User, args.Target); + } + + private void UseDelay(EntityUid playerEntity, EntityUid targetEntity) + { + + var time = 7 -_skills.GetSkillLevel(playerEntity, "Agility") * 0.3f; + var sdoAfter = new DoAfterArgs(EntityManager, + playerEntity, + time, + new SailUseEvent(), + targetEntity, + playerEntity) + { + MovementThreshold = 0.5f, + BreakOnMove = true, + CancelDuplicate = true, + DistanceThreshold = 2, + BreakOnDamage = true, + RequireCanInteract = false, + BreakOnDropItem = true, + BreakOnHandChange = true, + }; + _doAfter.TryStartDoAfter(sdoAfter); + } + + private void OnUse(EntityUid uid, SailComponent component, SailUseEvent args) + { + if (args.Cancelled) + return; + + Rotate(uid); + } + + private void Rotate(EntityUid uid) + { + _transform.SetWorldRotation(uid, _cfg.GetCVar(ShipsCCVars.WindRotation)/180); + } + + private void OnGetAlternativeVerbs(GetVerbsEvent ev) + { + if (ev.User == ev.Target || IsClientSide(ev.Target)) + return; + + if (!TryComp(ev.Target, out var sail)) + return; + var text = "Сложить"; + if (sail.Folded) + text = "Разложить"; + var verb = new AlternativeVerb() + { + Priority = 10, + Act = () => TryFold(ev.User, ev.Target), + Impact = LogImpact.Low, + Text = Loc.GetString(text), + }; + ev.Verbs.Add(verb); + } + + private void TryFold(EntityUid playerEntity, EntityUid targetEntity) + { + var time = 7 -_skills.GetSkillLevel(playerEntity, "Agility") * 0.15f - _skills.GetSkillLevel(playerEntity, "Intelligence") * 0.15f; + var sdoAfter = new DoAfterArgs(EntityManager, + playerEntity, + time, + new SailFoldEvent(), + targetEntity, + playerEntity) + { + MovementThreshold = 0.5f, + BreakOnMove = true, + CancelDuplicate = true, + DistanceThreshold = 2, + BreakOnDamage = true, + RequireCanInteract = false, + BreakOnDropItem = true, + BreakOnHandChange = true, + NeedHand = true, + }; + _doAfter.TryStartDoAfter(sdoAfter); + } +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Sea/SeaComponent.cs b/Content.Shared/Imperial/Medieval/Ships/Sea/SeaComponent.cs new file mode 100644 index 00000000000..5479917fa29 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Sea/SeaComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Imperial.Medieval.Ships.Sea; + +/// +/// Компонент моря +/// +[RegisterComponent] +public sealed partial class SeaComponent : Component +{ + [DataField("Disabled")] + public bool Disabled; +} diff --git a/Content.Shared/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningComponent.cs b/Content.Shared/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningComponent.cs new file mode 100644 index 00000000000..b2e79164a36 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/ShipDrowning/ShipDrowningComponent.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Imperial.Medieval.Ships.ShipDrowning; + +/// +/// This is used for... +/// +[RegisterComponent] +public sealed partial class ShipDrowningComponent : Component +{ + /// + /// Уровень затоплености + /// + [DataField("DrownLevel")] + public int DrownLevel; + /// + /// Максимальный уровень затоплености + /// + [DataField("DrownMaxLevel")] + public float DrownMaxLevel = 10000000000; +} diff --git a/Content.Shared/Imperial/Medieval/Ships/Wind/SharedWindSystem.cs b/Content.Shared/Imperial/Medieval/Ships/Wind/SharedWindSystem.cs new file mode 100644 index 00000000000..40e9eea46d4 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Ships/Wind/SharedWindSystem.cs @@ -0,0 +1,18 @@ +using System.Numerics; +using Robust.Shared.Maths; + +namespace Content.Shared.Imperial.Medieval.Ships.Wind; + +/// +/// This handles wind force calculations for sails. +/// +public sealed class SharedWindSystem : EntitySystem +{ + /// + public override void Initialize() + { + // Initialization logic here if needed + } +} + + diff --git a/Content.Shared/Imperial/Medieval/Skills/Systems/SharedSkillsSystem.cs b/Content.Shared/Imperial/Medieval/Skills/Systems/SharedSkillsSystem.cs index 02493f6648a..9250b2de118 100644 --- a/Content.Shared/Imperial/Medieval/Skills/Systems/SharedSkillsSystem.cs +++ b/Content.Shared/Imperial/Medieval/Skills/Systems/SharedSkillsSystem.cs @@ -81,4 +81,11 @@ public static int GetPointsCost(int level) return sum; } + + public int GetSkillLevel(EntityUid uid, string skill) + { + if (!TryComp(uid, out var skillComponent)) + return 1; + return skillComponent.Levels.GetValueOrDefault(skill, 1); + } } diff --git a/Content.Shared/Rotatable/RotatableComponent.cs b/Content.Shared/Rotatable/RotatableComponent.cs index 336c484d8c0..c13cc1425f0 100644 --- a/Content.Shared/Rotatable/RotatableComponent.cs +++ b/Content.Shared/Rotatable/RotatableComponent.cs @@ -11,18 +11,18 @@ public sealed partial class RotatableComponent : Component /// /// If true, this entity can be rotated even while anchored. /// - [DataField, AutoNetworkedField] + [DataField("RotateWhileAnchored"), AutoNetworkedField] public bool RotateWhileAnchored; /// /// If true, will rotate entity in players direction when pulled /// - [DataField, AutoNetworkedField] + [DataField("RotateWhilePulling"), AutoNetworkedField] public bool RotateWhilePulling = true; /// /// The angular value to change when using the rotate verbs. /// - [DataField, AutoNetworkedField] + [DataField("Increment"), AutoNetworkedField] public Angle Increment = Angle.FromDegrees(90); } diff --git a/Resources/Prototypes/Imperial/Medieval/Seas/Islands/Islands.yml b/Resources/Prototypes/Imperial/Medieval/Seas/Islands/Islands.yml new file mode 100644 index 00000000000..c8483534e0c --- /dev/null +++ b/Resources/Prototypes/Imperial/Medieval/Seas/Islands/Islands.yml @@ -0,0 +1,20 @@ +- type: island + id: PirateIslands + size: 20 + spawnWeight: 20 + name: "Пиратский остров" + +- type: island + id: FrendlyIslands + name: "Дружелюбный остров" + size: 5 + spawnWeight: 5 + +- type: island + id: VolcanicIsland + name: "Вулканический остров" + size: 3 + spawnWeight: 8 + + + diff --git a/Resources/Prototypes/Imperial/Medieval/Ships/oar.yml b/Resources/Prototypes/Imperial/Medieval/Ships/oar.yml new file mode 100644 index 00000000000..961cd197858 --- /dev/null +++ b/Resources/Prototypes/Imperial/Medieval/Ships/oar.yml @@ -0,0 +1,32 @@ +- type: entity + name: Весло + parent: BaseItem + id: MedievalOar + description: Обычное деревянное весло которое всем своим видом вселяет в тебя желание кого то ударить им. + components: + - type: QuestItem + contractName: "любое двуручное оружие" + - type: Sharp + - type: Sprite + sprite: Imperial/Medieval/Ships/Oar.rsi + state: base + - type: MeleeParry + parryChanse: 0.2 + - type: MeleeWeapon + animation: CP14WeaponArcThrust + wideAnimation: CP14WeaponArcSlash + cPSwingBeverage: true + cPAnimationOffset: 2 + clickDamageModifier: 1.32 + wideAnimationRotation: -140 + attackRate: 0.65 + range: 2.75 + angle: 0 + damage: + types: + Piercing: 0 + soundHit: + path: /Audio/Weapons/bladeslice.ogg + soundSwing: + path: /Audio/Imperial/Medieval/spear_swing.ogg + - type: Oar \ No newline at end of file diff --git a/Resources/Prototypes/Imperial/Medieval/Ships/wave.yml b/Resources/Prototypes/Imperial/Medieval/Ships/wave.yml new file mode 100644 index 00000000000..fe3d5178da2 --- /dev/null +++ b/Resources/Prototypes/Imperial/Medieval/Ships/wave.yml @@ -0,0 +1,57 @@ +- type: entity + id: BaseWave + name: meteor + description: You prefer them when they're burning up in the atmosphere. + abstract: true + components: + - type: Sprite + noRot: false + sprite: Objects/Misc/meteor.rsi + - type: Projectile + damage: {} + deleteOnCollide: false + - type: SpawnWave + - type: TimedDespawn + lifetime: 120 + - type: Clickable + - type: Physics + bodyType: Dynamic + bodyStatus: InAir + angularDamping: 0 + linearDamping: 0 + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeCircle + radius: 0.4 + density: 100 + hard: false + layer: + - MapGrid + mask: + - MapGrid + - Impassable + - type: Damageable + damageContainer: Inorganic + - type: TileFrictionModifier + modifier: 0 + +- type: entity + parent: BaseWave + id: WaveLarge + suffix: Large + components: + - type: Sprite + state: big + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 1200 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + collection: MetalBreak \ No newline at end of file diff --git a/Resources/Prototypes/Imperial/Medieval/decor.yml b/Resources/Prototypes/Imperial/Medieval/decor.yml index bcd464bb1e9..a7291baf9a8 100644 --- a/Resources/Prototypes/Imperial/Medieval/decor.yml +++ b/Resources/Prototypes/Imperial/Medieval/decor.yml @@ -5373,6 +5373,11 @@ layer: - WallLayer - type: InteractionOutline + - type: Sail + SailSize: 5 + - type: Rotatable + RotateWhileAnchored: true + Increment: 5 - type: entity name: small light diff --git a/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/base.png b/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/base.png new file mode 100644 index 00000000000..c86bb651eea Binary files /dev/null and b/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/base.png differ diff --git a/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/meta.json b/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/meta.json new file mode 100644 index 00000000000..5809be7d99a --- /dev/null +++ b/Resources/Textures/Imperial/Medieval/Ships/Oar.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": null, + "copyright": null, + "size": { + "x": 48, + "y": 48 + }, + "states": [ + { + "name": "base", + "directions": 4 + } + ] +} \ No newline at end of file