diff --git a/Content.Client/Imperial/Medieval/Bonfire/BonfireSystem.cs b/Content.Client/Imperial/Medieval/Bonfire/BonfireSystem.cs new file mode 100644 index 00000000000..a4e97064109 --- /dev/null +++ b/Content.Client/Imperial/Medieval/Bonfire/BonfireSystem.cs @@ -0,0 +1,26 @@ +using Robust.Client.GameObjects; +using Content.Shared.Imperial.Medieval.Bonfire; + +namespace Content.Client.Imperial.Medieval.Bonfire; + +public sealed class BonfireSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + } + private void OnAppearanceChange(EntityUid uid, BonfireComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (_appearance.TryGetData(uid, BonfireVisualLayers.Fire, out var isFireVisible, args.Component)) + { + args.Sprite.LayerSetVisible(BonfireVisualLayers.Fire, isFireVisible); + } + } +} diff --git a/Content.Server/Imperial/Medieval/Bonfire/BonfireSystem.cs b/Content.Server/Imperial/Medieval/Bonfire/BonfireSystem.cs new file mode 100644 index 00000000000..201820f4378 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Bonfire/BonfireSystem.cs @@ -0,0 +1,217 @@ +using Robust.Server.GameObjects; +using Content.Shared.Interaction; +using Content.Shared.Stacks; +using Robust.Shared.Audio; +using Content.Server.Audio; +using Robust.Shared.Audio.Systems; +using Content.Shared.Imperial.Medieval.Bonfire; +using Content.Server.Imperial.Medieval.Igniter; +using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Tag; +using Content.Shared.Audio; +using Content.Shared.Placeable; +using Content.Shared.DoAfter; +using Content.Server.Temperature.Systems; + +namespace Content.Server.Imperial.Medieval.Bonfire; + +public sealed class BonfireSystem : EntitySystem +{ + [Dependency] protected readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPointLightSystem _lights = default!; + [Dependency] private readonly AmbientSoundSystem _ambientSound = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedStackSystem _stack = default!; + [Dependency] private readonly TemperatureSystem _temperature = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + + private const float FuelDecreasePerSecond = 0.11f; + private const float BoardFuelAmount = 20f; + private const float LogFuelAmount = 40f; + private const float SheetFuelAmount = 15f; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnIgnitionDoAfter); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var bonfire, out var placer)) + { + if (bonfire.IsLit == BonfireVisuals.Off) + continue; + + var heat = bonfire.HeatingPower * frameTime; + foreach (var ent in placer.PlacedEntities) + { + _temperature.ChangeHeat(ent, heat); + } + + bonfire.CurrentFuel = MathF.Max(0, bonfire.CurrentFuel - FuelDecreasePerSecond * frameTime); + UpdateBonfireVisuals(uid, bonfire); + + if (bonfire.CurrentFuel <= 0) + { + bonfire.CurrentFuel = 0; + ExtinguishBonfire(uid, bonfire); + } + } + } + + private void UpdateBonfireVisuals(EntityUid uid, BonfireComponent component) + { + var fuelPercentage = component.CurrentFuel / component.MaxFuel; + var radius = fuelPercentage switch + { + > 0.8f => 5f, + > 0.5f => 4.5f, + > 0.3f => 3.5f, + > 0.1f => 2.8f, + _ => 2f + }; + + if (TryComp(uid, out var light)) + { + _lights.SetRadius(uid, radius, light); + } + + var energy = fuelPercentage switch + { + > 0.8f => 3f, + > 0.5f => 2.5f, + > 0.3f => 2f, + > 0.1f => 1.5f, + _ => 1f + }; + _lights.SetEnergy(uid, energy); + } + + private void OnExamined(EntityUid uid, BonfireComponent component, ExaminedEvent args) + { + if (component.IsLit == BonfireVisuals.Off) + { + args.PushText("Костёр не горит."); + return; + } + + var fuelLevel = (int)(component.CurrentFuel / component.MaxFuel * 100); + var fuelDescription = fuelLevel switch + { + > 80 => "яркое", + > 50 => "теплое", + > 20 => "обычное", + _ => "тухлое" + }; + + args.PushText($"У костра {fuelDescription} пламя."); + } + + private void OnInteractUsing(EntityUid uid, BonfireComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + if (component.IsLit == BonfireVisuals.Off && HasComp(args.Used)) + { + if (component.CurrentFuel <= 0) + { + _popupSystem.PopupEntity("Нет топлива для розжига!", uid, args.User, PopupType.Medium); + return; + } + + var doAfterArgs = new DoAfterArgs(EntityManager, args.User, 2f, new IgnitionDoAfterEvent(), uid, args.Target, args.Used) + { + BreakOnMove = true, + BreakOnDamage = true, + NeedHand = true, + BreakOnDropItem = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + args.Handled = true; + return; + } + + if (_tagSystem.HasTag(args.Used, "Wooden") || _tagSystem.HasTag(args.Used, "Log") || _tagSystem.HasTag(args.Used, "Sheet")) + { + float fuelAmount = 0; + + switch (true) + { + case bool when _tagSystem.HasTag(args.Used, "Log"): + fuelAmount = LogFuelAmount; + break; + case bool when _tagSystem.HasTag(args.Used, "Sheet"): + fuelAmount = SheetFuelAmount; + break; + case bool when _tagSystem.HasTag(args.Used, "Wooden"): + fuelAmount = BoardFuelAmount; + break; + } + + if (TryComp(args.Used, out var stack) && stack.Count > 1) + { + _stack.SetCount(args.Used, stack.Count - 1); + } + else + { + EntityManager.DeleteEntity(args.Used); + } + + component.CurrentFuel = Math.Min(component.CurrentFuel + fuelAmount, component.MaxFuel); + _popupSystem.PopupEntity("Вы подложили топливо в костер", uid, args.User, PopupType.Medium); + UpdateBonfireVisuals(uid, component); + args.Handled = true; + } + } + + private void OnIgnitionDoAfter(EntityUid uid, BonfireComponent component, IgnitionDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + LightBonfire(uid, component); + args.Handled = true; + } + + private void LightBonfire(EntityUid uid, BonfireComponent component) + { + component.IsLit = BonfireVisuals.Fire; + + EnsureComp(uid); + + _lights.SetEnabled(uid, true); + _lights.SetColor(uid, Color.FromHex("#FFC90C")); + _lights.SetEnergy(uid, 3); + + UpdateBonfireVisuals(uid, component); + + EnsureComp(uid); + + _ambientSound.SetSound(uid, new SoundPathSpecifier("/Audio/Ambience/Objects/fireplace.ogg")); + _ambientSound.SetRange(uid, 5); + _ambientSound.SetVolume(uid, -5); + _ambientSound.SetAmbience(uid, true); + + _appearance.SetData(uid, BonfireVisualLayers.Fire, true); + _audio.PlayPvs(new SoundPathSpecifier(component.IgnitionSound), uid); + } + + private void ExtinguishBonfire(EntityUid uid, BonfireComponent component) + { + component.IsLit = BonfireVisuals.Off; + _lights.SetEnabled(uid, false); + _ambientSound.SetAmbience(uid, false); + _appearance.SetData(uid, BonfireVisualLayers.Fire, false); + _audio.PlayPvs(new SoundPathSpecifier(component.ExtinguishSound), uid); + } +} diff --git a/Content.Shared/Imperial/Medieval/Bonfire/BonfireComponent.cs b/Content.Shared/Imperial/Medieval/Bonfire/BonfireComponent.cs new file mode 100644 index 00000000000..fd8cdf784cf --- /dev/null +++ b/Content.Shared/Imperial/Medieval/Bonfire/BonfireComponent.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Serialization; +using Robust.Shared.GameStates; +using Content.Shared.DoAfter; + +namespace Content.Shared.Imperial.Medieval.Bonfire; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class BonfireComponent : Component +{ + [DataField] + public string IgnitionSound = "/Audio/Items/Flare/flare_on.ogg"; + + [DataField] + public string ExtinguishSound = "/Audio/Items/candle_blowing.ogg"; + + [DataField, AutoNetworkedField] + public BonfireVisuals IsLit = BonfireVisuals.Off; + + [DataField] + public float MaxFuel = 100f; + + [DataField, AutoNetworkedField] + public float CurrentFuel = 0f; + + [DataField] + public float HeatingPower = 1200f; +} + +[Serializable, NetSerializable] +public sealed partial class IgnitionDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public enum BonfireVisuals : byte +{ + Off, + Fire +} + +[Serializable, NetSerializable] +public enum BonfireVisualLayers : byte +{ + Bonfire, + Fire +} diff --git a/Resources/Locale/ru-RU/Imperial/Medieval/medieval.ftl b/Resources/Locale/ru-RU/Imperial/Medieval/medieval.ftl index a9916132823..505d8b7d5e1 100644 --- a/Resources/Locale/ru-RU/Imperial/Medieval/medieval.ftl +++ b/Resources/Locale/ru-RU/Imperial/Medieval/medieval.ftl @@ -290,6 +290,10 @@ ent-MedievalShovel = лопатка ent-MedievalCampfire = костёр .desc = Оружие инквизиции... .suffix = { "Средневековье, станок" } +ent-MedievalBonfire = костёр + .desc = Источник света и тепла! + .suffix = { "Средневековье" } + ent-MedievalMobFox = лиса .desc = Она имеет густой рыжий мех и длинный хвост. Некоторые люди считают, что лисы - это коварные создания, но на самом деле они очень умные и адаптивные. .suffix = { "Средневековье, моб" } diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 333354531cd..68414dc198e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -197,6 +197,11 @@ - type: Produce seedId: towercap - type: Log + #imperial-medieval-start + - type: Tag + tags: + - Log + #imperial-medieval-end - type: entity name: steel-cap log diff --git a/Resources/Prototypes/Imperial/Medieval/medieval.yml b/Resources/Prototypes/Imperial/Medieval/medieval.yml index 5348ca8434e..cbc794d9d28 100644 --- a/Resources/Prototypes/Imperial/Medieval/medieval.yml +++ b/Resources/Prototypes/Imperial/Medieval/medieval.yml @@ -7738,3 +7738,73 @@ doAfter: 4 - node: MedievalMortarTool entity: MedievalMortarTool + +- type: entity + id: MedievalBonfire + parent: BaseStructure + name: Campfire + description: A source of light and heat! + components: + - type: Sprite + noRot: true + sprite: Imperial/Medieval/Decor/campfire.rsi + layers: + - state: wood + map: ["enum.BonfireVisualLayers.Bonfire"] + - state: fire + map: ["enum.BonfireVisualLayers.Fire"] + visible: false + - type: Appearance + - type: Damageable + damageContainer: StructuralInorganic + damageModifierSet: Wood + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 50 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Construction + graph: Campfire + node: campfire + - type: Bonfire + - type: ItemPlacer + maxEntities: 2 + whitelist: + components: + - Temperature + - type: PlaceableSurface + +- type: construction + name: Костер + id: CampfireConstruction + graph: Campfire + startNode: start + targetNode: campfire + category: construction-category-misc + description: Тёплый огонь для ночных посиделок. + icon: + sprite: Imperial/Medieval/Decor/campfire.rsi + state: wood + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: false + conditions: + - !type:TileNotBlocked + +- type: constructionGraph + id: Campfire + start: start + graph: + - node: start + edges: + - to: campfire + steps: + - tag: Log + name: "Бревно" + doAfter: 3 + + - node: campfire + entity: MedievalBonfire diff --git a/Resources/Prototypes/Imperial/Medieval/tags.yml b/Resources/Prototypes/Imperial/Medieval/tags.yml index 77b789119ec..7990a015ba7 100644 --- a/Resources/Prototypes/Imperial/Medieval/tags.yml +++ b/Resources/Prototypes/Imperial/Medieval/tags.yml @@ -81,3 +81,6 @@ - type: Tag id: GoblinArmor + +- type: Tag + id: Log diff --git a/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/fire.png b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/fire.png new file mode 100644 index 00000000000..6ede4d7abe7 Binary files /dev/null and b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/fire.png differ diff --git a/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/meta.json b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/meta.json new file mode 100644 index 00000000000..b464e35a1a1 --- /dev/null +++ b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "this content is under ICLA licence, read more on https://wiki.imperialspace.net/icla", + "copyright": "by prazat911", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "wood" + }, + { + "name": "wood-burned" + }, + { + "name": "fire", + "delays": [ + [ + 0.3, + 0.3, + 0.3 + ] + ] + } + ] +} diff --git a/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood-burned.png b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood-burned.png new file mode 100644 index 00000000000..d3ddcf6594c Binary files /dev/null and b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood-burned.png differ diff --git a/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood.png b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood.png new file mode 100644 index 00000000000..58f889785ee Binary files /dev/null and b/Resources/Textures/Imperial/Medieval/Decor/campfire.rsi/wood.png differ