diff --git a/.editorconfig b/.editorconfig index 3e44d1a281..370c3f9dee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -72,7 +72,7 @@ csharp_style_expression_bodied_constructors = false:suggestion #csharp_style_expression_bodied_indexers = true:silent #csharp_style_expression_bodied_lambdas = true:silent #csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_methods = true:suggestion #csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:suggestion diff --git a/Content.Client/Paint/PaintVisualizerSystem.cs b/Content.Client/Paint/PaintVisualizerSystem.cs new file mode 100644 index 0000000000..a00314cd68 --- /dev/null +++ b/Content.Client/Paint/PaintVisualizerSystem.cs @@ -0,0 +1,101 @@ +using Robust.Client.GameObjects; +using static Robust.Client.GameObjects.SpriteComponent; +using Content.Shared.Clothing; +using Content.Shared.Hands; +using Content.Shared.Paint; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client.Paint; + +public sealed class PaintedVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHeldVisualsUpdated); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnEquipmentVisualsUpdated); + } + + + protected override void OnAppearanceChange(EntityUid uid, PaintedComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null + || !_appearance.TryGetData(uid, PaintVisuals.Painted, out bool isPainted)) + return; + + var shader = _protoMan.Index(component.ShaderName).Instance(); + foreach (var spriteLayer in args.Sprite.AllLayers) + { + if (spriteLayer is not Layer layer) + continue; + + if (layer.Shader == null || layer.Shader == shader) + { + layer.Shader = shader; + layer.Color = component.Color; + } + } + } + + private void OnShutdown(EntityUid uid, PaintedComponent component, ref ComponentShutdown args) + { + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + component.BeforeColor = sprite.Color; + + if (Terminating(uid)) + return; + + foreach (var spriteLayer in sprite.AllLayers) + { + if (spriteLayer is not Layer layer + || layer.Shader != _protoMan.Index(component.ShaderName).Instance()) + continue; + + layer.Shader = null; + if (layer.Color == component.Color) + layer.Color = component.BeforeColor; + } + } + + private void OnHeldVisualsUpdated(EntityUid uid, PaintedComponent component, HeldVisualsUpdatedEvent args) => + UpdateVisuals(component, args); + private void OnEquipmentVisualsUpdated(EntityUid uid, PaintedComponent component, EquipmentVisualsUpdatedEvent args) => + UpdateVisuals(component, args); + private void UpdateVisuals(PaintedComponent component, EntityEventArgs args) + { + var layers = new HashSet(); + var entity = EntityUid.Invalid; + + switch (args) + { + case HeldVisualsUpdatedEvent hgs: + layers = hgs.RevealedLayers; + entity = hgs.User; + break; + case EquipmentVisualsUpdatedEvent eqs: + layers = eqs.RevealedLayers; + entity = eqs.Equipee; + break; + } + + if (layers.Count == 0 || !TryComp(entity, out SpriteComponent? sprite)) + return; + + foreach (var revealed in layers) + { + if (!sprite.LayerMapTryGet(revealed, out var layer)) + continue; + + sprite.LayerSetShader(layer, component.ShaderName); + sprite.LayerSetColor(layer, component.Color); + } + } +} diff --git a/Content.Server/Paint/PaintSystem.cs b/Content.Server/Paint/PaintSystem.cs new file mode 100644 index 0000000000..bdda9ed887 --- /dev/null +++ b/Content.Server/Paint/PaintSystem.cs @@ -0,0 +1,202 @@ +using Content.Shared.Popups; +using Content.Shared.Paint; +using Content.Shared.Sprite; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Server.Chemistry.Containers.EntitySystems; +using Robust.Shared.Audio.Systems; +using Content.Shared.Humanoid; +using Robust.Shared.Utility; +using Content.Shared.Verbs; +using Content.Shared.SubFloor; +using Content.Server.Nutrition.Components; +using Content.Shared.Inventory; +using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; + +namespace Content.Server.Paint; + +/// +/// Colors target and consumes reagent on each color success. +/// +public sealed class PaintSystem : SharedPaintSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnPaint); + SubscribeLocalEvent>(OnPaintVerb); + } + + + private void OnInteract(EntityUid uid, PaintComponent component, AfterInteractEvent args) + { + if (!args.CanReach + || args.Target is not { Valid: true } target) + return; + + PrepPaint(uid, component, target, args.User); + } + + private void OnPaintVerb(EntityUid uid, PaintComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var paintText = Loc.GetString("paint-verb"); + + var verb = new UtilityVerb() + { + Act = () => + { + PrepPaint(uid, component, args.Target, args.User); + }, + + Text = paintText, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/paint.svg.192dpi.png")) + }; + args.Verbs.Add(verb); + } + + private void PrepPaint(EntityUid uid, PaintComponent component, EntityUid target, EntityUid user) => + _doAfterSystem.TryStartDoAfter( + new DoAfterArgs( + EntityManager, + user, + component.Delay, + new PaintDoAfterEvent(), + uid, + target: target, + used: uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + NeedHand = true, + BreakOnHandChange = true, + }); + + private void OnPaint(Entity entity, ref PaintDoAfterEvent args) + { + if (args.Target == null || args.Used == null || args.Handled || args.Cancelled || args.Target is not { Valid: true } target) + return; + + Paint(entity, target, args.User); + args.Handled = true; + } + + public void Paint(Entity entity, EntityUid target, EntityUid user) + { + if (!_openable.IsOpen(entity)) + { + _popup.PopupEntity(Loc.GetString("paint-closed", ("used", entity)), user, user, PopupType.Medium); + return; + } + + if (HasComp(target) || HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("paint-failure-painted", ("target", target)), user, user, PopupType.Medium); + return; + } + + if (!entity.Comp.Whitelist?.IsValid(target, EntityManager) == true + || !entity.Comp.Blacklist?.IsValid(target, EntityManager) == false + || HasComp(target) || HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("paint-failure", ("target", target)), user, user, PopupType.Medium); + return; + } + + if (CanPaint(entity, target)) + { + EnsureComp(target, out var paint); + EnsureComp(target); + + paint.Color = entity.Comp.Color; + _audio.PlayPvs(entity.Comp.Spray, entity); + paint.Enabled = true; + + // Paint any clothing the target is wearing + if (HasComp(target) + && _inventory.TryGetSlots(target, out var slotDefinitions)) + foreach (var slot in slotDefinitions) + { + if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) + || HasComp(slotEnt.Value) + || !entity.Comp.Whitelist?.IsValid(slotEnt.Value, EntityManager) != true + || !entity.Comp.Blacklist?.IsValid(slotEnt.Value, EntityManager) != false + || HasComp(slotEnt.Value) + || HasComp(slotEnt.Value)) + continue; + + EnsureComp(slotEnt.Value, out var slotToPaint); + EnsureComp(slotEnt.Value); + slotToPaint.Color = entity.Comp.Color; + _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); + Dirty(slotEnt.Value, slotToPaint); + } + + _popup.PopupEntity(Loc.GetString("paint-success", ("target", target)), user, user, PopupType.Medium); + _appearanceSystem.SetData(target, PaintVisuals.Painted, true); + Dirty(target, paint); + return; + } + + if (!CanPaint(entity, target)) + _popup.PopupEntity(Loc.GetString("paint-empty", ("used", entity)), user, user, PopupType.Medium); + } + + public void Paint(EntityWhitelist whitelist, EntityWhitelist blacklist, EntityUid target, Color color) + { + if (!whitelist.IsValid(target, EntityManager) + || blacklist.IsValid(target, EntityManager)) + return; + + EnsureComp(target, out var paint); + EnsureComp(target); + + paint.Color = color; + paint.Enabled = true; + + if (HasComp(target) + && _inventory.TryGetSlots(target, out var slotDefinitions)) + foreach (var slot in slotDefinitions) + { + if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) + || !whitelist.IsValid(slotEnt.Value, EntityManager) + || blacklist.IsValid(slotEnt.Value, EntityManager)) + continue; + + EnsureComp(slotEnt.Value, out var slotToPaint); + EnsureComp(slotEnt.Value); + slotToPaint.Color = color; + _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); + Dirty(slotEnt.Value, slotToPaint); + } + + _appearanceSystem.SetData(target, PaintVisuals.Painted, true); + Dirty(target, paint); + } + + private bool CanPaint(Entity reagent, EntityUid target) + { + if (HasComp(target) + || HasComp(target) + || !_solutionContainer.TryGetSolution(reagent.Owner, reagent.Comp.Solution, out _, out var solution)) + return false; + var quantity = solution.RemoveReagent(reagent.Comp.Reagent, reagent.Comp.ConsumptionUnit); + return (quantity > 0); + } +} diff --git a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs index 4c533ede3a..3549587aa5 100644 --- a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs +++ b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs @@ -99,6 +99,7 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI if (entityToPlaceInHands != null) { _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value); + _audio.PlayPvs(component.Sound, entityToPlaceInHands.Value); } } } diff --git a/Content.Shared/Clothing/ClothingEvents.cs b/Content.Shared/Clothing/ClothingEvents.cs index 83afea4597..1e74b4b56c 100644 --- a/Content.Shared/Clothing/ClothingEvents.cs +++ b/Content.Shared/Clothing/ClothingEvents.cs @@ -4,18 +4,11 @@ namespace Content.Shared.Clothing; -/// -/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite -/// -public sealed class GetEquipmentVisualsEvent : EntityEventArgs +/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite +public sealed class GetEquipmentVisualsEvent(EntityUid equipee, string slot) : EntityEventArgs { - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - + public readonly EntityUid Equipee = equipee; + public readonly string Slot = slot; /// /// The layers that will be added to the entity that is wearing this item. /// @@ -23,12 +16,6 @@ public sealed class GetEquipmentVisualsEvent : EntityEventArgs /// Note that the actual ordering of the layers depends on the order in which they are added to this list; /// public List<(string, PrototypeLayerData)> Layers = new(); - - public GetEquipmentVisualsEvent(EntityUid equipee, string slot) - { - Equipee = equipee; - Slot = slot; - } } /// @@ -37,26 +24,12 @@ public GetEquipmentVisualsEvent(EntityUid equipee, string slot) /// /// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes) /// -public sealed class EquipmentVisualsUpdatedEvent : EntityEventArgs +public sealed class EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet revealedLayers) : EntityEventArgs { - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - - /// - /// The layers that this item is now revealing. - /// - public HashSet RevealedLayers; - - public EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet revealedLayers) - { - Equipee = equipee; - Slot = slot; - RevealedLayers = revealedLayers; - } + public readonly EntityUid Equipee = equipee; + public readonly string Slot = slot; + /// The layers that this item is now revealing. + public HashSet RevealedLayers = revealedLayers; } public sealed partial class ToggleMaskEvent : InstantActionEvent { } diff --git a/Content.Shared/Paint/PaintComponent.cs b/Content.Shared/Paint/PaintComponent.cs new file mode 100644 index 0000000000..dc35b8fd43 --- /dev/null +++ b/Content.Shared/Paint/PaintComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; +using Robust.Shared.GameStates; + +namespace Content.Shared.Paint; + +/// Entity when used on another entity will paint target entity +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedPaintSystem))] +public sealed partial class PaintComponent : Component +{ + /// Noise made when paint gets applied + [DataField] + public SoundSpecifier Spray = new SoundPathSpecifier("/Audio/Effects/spray2.ogg"); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Whitelist; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Blacklist; + + /// How long the doafter will take + [DataField] + public int Delay = 2; + + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#c62121"); + + /// Solution on the entity that contains the paint + [DataField] + public string Solution = "drink"; + + /// Reagent that will be used as paint + [DataField, AutoNetworkedField] + public ProtoId Reagent = "SpaceGlue"; + + /// Reagent consumption per use + [DataField] + public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5); + + [DataField] + public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6); +} diff --git a/Content.Shared/Paint/PaintDoAfterEvent.cs b/Content.Shared/Paint/PaintDoAfterEvent.cs new file mode 100644 index 0000000000..a6f7b951ec --- /dev/null +++ b/Content.Shared/Paint/PaintDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +[Serializable, NetSerializable] +public sealed partial class PaintDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Paint/PaintRemoverComponent.cs b/Content.Shared/Paint/PaintRemoverComponent.cs new file mode 100644 index 0000000000..9fee3c90cb --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; + +namespace Content.Shared.Paint; + +/// Removes paint from an entity that was painted with spray paint +[RegisterComponent, NetworkedComponent] +[Access(typeof(PaintRemoverSystem))] +public sealed partial class PaintRemoverComponent : Component +{ + /// Sound played when target is cleaned + [DataField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg"); + + [DataField] + public float CleanDelay = 2f; +} diff --git a/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs new file mode 100644 index 0000000000..ec2a2e0324 --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +[Serializable, NetSerializable] +public sealed partial class PaintRemoverDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Paint/PaintRemoverSystem.cs b/Content.Shared/Paint/PaintRemoverSystem.cs new file mode 100644 index 0000000000..e2565e0f22 --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverSystem.cs @@ -0,0 +1,96 @@ +using Content.Shared.Popups; +using Content.Shared.Interaction; +using Content.Shared.DoAfter; +using Content.Shared.Verbs; +using Content.Shared.Sprite; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; + +namespace Content.Shared.Paint; + +public sealed class PaintRemoverSystem : SharedPaintSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent>(OnPaintRemoveVerb); + } + + + private void OnInteract(EntityUid uid, PaintRemoverComponent component, AfterInteractEvent args) + { + if (args.Handled + || !args.CanReach + || args.Target is not { Valid: true } target + || !HasComp(target)) + return; + + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new PaintRemoverDoAfterEvent(), uid, args.Target, uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnDamage = true, + MovementThreshold = 1.0f, + }); + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PaintRemoverComponent component, DoAfterEvent args) + { + if (args.Cancelled + || args.Handled + || args.Args.Target == null + || args.Target is not { Valid: true } target + || !TryComp(target, out PaintedComponent? paint)) + return; + + paint.Enabled = false; + _audio.PlayPredicted(component.Sound, target, args.User); + _popup.PopupClient(Loc.GetString("paint-removed", ("target", target)), args.User, args.User, PopupType.Medium); + _appearanceSystem.SetData(target, PaintVisuals.Painted, false); + RemComp(target); + Dirty(target, paint); + + args.Handled = true; + } + + private void OnPaintRemoveVerb(EntityUid uid, PaintRemoverComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var verb = new UtilityVerb() + { + Text = Loc.GetString("paint-remove-verb"), + Act = () => + { + _doAfter.TryStartDoAfter( + new DoAfterArgs( + EntityManager, + args.User, + component.CleanDelay, + new PaintRemoverDoAfterEvent(), + uid, + args.Target, + uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnDamage = true, + MovementThreshold = 1.0f, + }); + }, + }; + + args.Verbs.Add(verb); + } +} diff --git a/Content.Shared/Paint/PaintedComponent.cs b/Content.Shared/Paint/PaintedComponent.cs new file mode 100644 index 0000000000..3d2deee45e --- /dev/null +++ b/Content.Shared/Paint/PaintedComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +/// Component applied to target entity when painted +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PaintedComponent : Component +{ + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#2cdbd5"); + + /// Used to remove the color when the component is removed + [DataField, AutoNetworkedField] + public Color BeforeColor; + + [DataField, AutoNetworkedField] + public bool Enabled; + + // Not using ProtoId because ShaderPrototype is in Robust.Client + [DataField, AutoNetworkedField] + public string ShaderName = "Greyscale"; +} + +[Serializable, NetSerializable] +public enum PaintVisuals : byte +{ + Painted, +} diff --git a/Content.Shared/Paint/SharedPaintSystem.cs b/Content.Shared/Paint/SharedPaintSystem.cs new file mode 100644 index 0000000000..4707e94277 --- /dev/null +++ b/Content.Shared/Paint/SharedPaintSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Paint; + +public abstract class SharedPaintSystem : EntitySystem +{ + public virtual void UpdateAppearance(EntityUid uid, PaintedComponent? component = null) { } +} diff --git a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs index 529e321f8d..fa04a50f8b 100644 --- a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs +++ b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Doors.Components; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Paint; using Content.Shared.SprayPainter.Components; using Content.Shared.SprayPainter.Prototypes; using Robust.Shared.Audio.Systems; @@ -129,6 +130,8 @@ private void OnAirlockInteract(Entity ent, ref Intera return; } + RemComp(ent); + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.AirlockSprayTime, new SprayPainterDoorDoAfterEvent(sprite, style.Department), args.Used, target: ent, used: args.Used) { BreakOnTargetMove = true, diff --git a/Resources/Locale/en-US/paint/paint.ftl b/Resources/Locale/en-US/paint/paint.ftl new file mode 100644 index 0000000000..200b1f6e3f --- /dev/null +++ b/Resources/Locale/en-US/paint/paint.ftl @@ -0,0 +1,8 @@ +paint-success = {THE($target)} has been covered in paint! +paint-failure = Can't cover {THE($target)} in paint! +paint-failure-painted = {THE($target)} is already covered in paint! +paint-empty = {THE($used)} is empty! +paint-removed = You clean off the paint! +paint-closed = You must open {THE($used)} first! +paint-verb = Paint +paint-remove-verb = Remove Paint diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml index 68bb4a6b84..771c05db0d 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml @@ -68,6 +68,16 @@ category: cargoproduct-category-name-fun group: market +- type: cargoProduct + id: FunSprayPaints + icon: + sprite: Objects/Fun/spraycans.rsi + state: death2_cap + product: CrateFunSprayPaints + cost: 2000 + category: Fun + group: market + - type: cargoProduct id: FunParty icon: diff --git a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml index b55bdd4832..a6f12bc727 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml @@ -248,14 +248,21 @@ contents: - id: SnapPopBox - id: CrazyGlue - amount: 2 - id: PlasticBanana + - id: FunnyPaint + orGroup: Paint + prob: 0.5 + - id: FunnyPaintYellow + orGroup: Paint + prob: 0.5 - id: WhoopieCushion - id: ToyHammer - id: MrChips - orGroup: GiftPool + prob: 0.5 + orGroup: Dummy - id: MrDips - orGroup: Giftpool + prob: 0.5 + orGroup: Dummy - id: RevolverCapGun - id: BalloonNT - id: ClothingShoesClownLarge @@ -288,6 +295,41 @@ amount: 15 prob: 0.05 +- type: entity + id: CrateFunSprayPaints + name: spray paint crate + description: a crate filled with spray paint. + parent: CratePlastic + suffix: Spray Paint + components: + - type: StorageFill + contents: + - id: SprayPaintBlue + amount: 2 + prob: 0.33 + - id: SprayPaintRed + amount: 2 + prob: 0.33 + - id: SprayPaintOrange + amount: 2 + prob: 0.33 + - id: SprayPaintBlack + amount: 2 + prob: 0.33 + - id: SprayPaintGreen + amount: 2 + prob: 0.33 + - id: SprayPaintPurple + amount: 2 + prob: 0.33 + - id: SprayPaintWhite + amount: 2 + prob: 0.33 + - id: DeathPaint + amount: 2 + - id: DeathPaintTwo + amount: 2 + - type: entity name: dartboard box set description: A box with everything you need for a fun game of darts. diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index eaf35667ef..feaedd924a 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -157,6 +157,10 @@ prob: 0.25 - id: StrangePill prob: 0.20 + - id: DeathPaint + prob: 0.05 + - id: DeathPaintTwo + prob: 0.05 - id: DrinkMopwataBottleRandom prob: 0.20 - id: ModularReceiver diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml index ae7e5bcf76..883182aae8 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml @@ -44,6 +44,7 @@ - CrateMaterialPlastic - CrateMaterialWood - CrateMaterialPlasteel + - CrateFunSprayPaints - CrateFunArtSupplies - CrateEngineeringCableLV - CrateEngineeringCableMV diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 4c820998ea..3259439258 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -176,7 +176,12 @@ - MaterialCloth10 - MaterialWoodPlank10 - ResearchDisk + - DeathPaint - Plunger + - SprayPaintBlue + - SprayPaintRed + - SprayPaintGreen + - SprayPaintOrange - TechnologyDisk - PowerCellMedium - PowerCellSmall diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 2778bf1278..6138bc6e96 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -71,6 +71,7 @@ tags: - Carp - DoorBumpOpener + - NoPaint - type: ReplacementAccent accent: genericAggressive - type: Speech diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index d466a34383..c3706cea2a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -109,3 +109,6 @@ - TelepathyPower - type: LanguageSpeaker - type: UniversalLanguageSpeaker + - type: Tag + tags: + - NoPaint diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index 6d90ac3ffe..7bb8547cfa 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -104,6 +104,7 @@ - type: Tag tags: - CannotSuicide + - NoPaint # From the uplink injector - type: entity @@ -223,6 +224,7 @@ tags: - CannotSuicide - FootstepSound + - NoPaint - type: Inventory templateId: holoclown - type: Hands diff --git a/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml new file mode 100644 index 0000000000..1b417f6cde --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml @@ -0,0 +1,292 @@ +# Base Paints +- type: entity + parent: BaseItem + id: PaintBase + name: spray paint + description: A tin of spray paint. + noSpawn: true + components: + - type: Appearance + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: clown_cap + layers: + - state: clown_cap + map: ["enum.OpenableVisuals.Layer"] + - type: Paint + consumptionUnit: 10 + blacklist: + tags: + - NoPaint + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: SolutionContainerManager + solutions: + drink: + maxVol: 50 + reagents: + - ReagentId: SpaceGlue + Quantity: 50 + - type: TrashOnSolutionEmpty + solution: drink + - type: Sealable + - type: Openable + sound: + path: /Audio/Effects/pop_high.ogg + closeable: true + closeSound: + path: /Audio/Effects/pop_high.ogg + +# Paints + +# funnypaint +- type: entity + parent: PaintBase + id: FunnyPaint + name: funny paint + description: A tin of funny paint, manufactured by Honk! Co. + components: + - type: Paint + color: "#fa74df" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: clown + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "clown"} + False: {state: "clown_cap"} + +- type: entity + parent: PaintBase + id: FunnyPaintYellow + name: funny paint + description: A tin of funny paint, manufactured by Honk! Co. + components: + - type: Paint + color: "#d5e028" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: clown + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: clown2_cap + layers: + - state: clown2_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "clown2"} + False: {state: "clown2_cap"} + +#death paint +- type: entity + parent: PaintBase + id: DeathPaint + components: + - type: Paint + color: "#ff20c8" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: death_cap + layers: + - state: death_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "death"} + False: {state: "death_cap"} + +- type: entity + parent: PaintBase + id: DeathPaintTwo + components: + - type: Paint + color: "#ff2020" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: death2_cap + layers: + - state: death2_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "death2"} + False: {state: "death2_cap"} + +#Sprays + +#Blue +- type: entity + parent: PaintBase + id: SprayPaintBlue + suffix: Blue + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#5890f7" + - type: Paint + color: "#5890f7" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#5890f7"} + False: {state: "spray_cap_colors" , color: "#5890f7"} + +#Red +- type: entity + parent: PaintBase + id: SprayPaintRed + suffix: Red + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#ff3b3b" + - type: Paint + color: "#ff3b3b" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#ff3b3b"} + False: {state: "spray_cap_colors" , color: "#ff3b3b"} + +#Green +- type: entity + parent: PaintBase + id: SprayPaintGreen + suffix: Green + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#73f170" + - type: Paint + color: "#73f170" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#73f170"} + False: {state: "spray_cap_colors" , color: "#73f170"} + +#Black +- type: entity + parent: PaintBase + id: SprayPaintBlack + suffix: Black + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#3a3a3a" + - type: Paint + color: "#3a3a3a" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#3a3a3a"} + False: {state: "spray_cap_colors" , color: "#3a3a3a"} + +#Orange +- type: entity + parent: PaintBase + id: SprayPaintOrange + suffix: Orange + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#f6a44b" + - type: Paint + color: "#f6a44b" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#f6a44b"} + False: {state: "spray_cap_colors" , color: "#f6a44b"} + +#Purple +- type: entity + parent: PaintBase + id: SprayPaintPurple + suffix: Purple + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#c063f5" + - type: Paint + color: "#c063f5" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#c063f5"} + False: {state: "spray_cap_colors" , color: "#c063f5"} + +#White +- type: entity + parent: PaintBase + id: SprayPaintWhite + suffix: White + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#f2f2f2" + - type: Paint + color: "#f2f2f2" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#f2f2f2"} + False: {state: "spray_cap_colors" , color: "#f2f2f2"} diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index e318d7b188..388ed24ae7 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -15,6 +15,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Material - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml index f486125ff6..95ecd3fe32 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml @@ -15,6 +15,7 @@ tags: - Sheet - Metal + - NoPaint - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index ae6770d631..c0d686e299 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -12,6 +12,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible @@ -110,6 +111,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: UserInterface interfaces: enum.RadialSelectorUiKey.Key: @@ -145,6 +147,7 @@ tags: - Plastic - Sheet + - NoPaint - type: Material - type: PhysicalComposition materialComposition: diff --git a/Resources/Prototypes/Entities/Objects/Materials/materials.yml b/Resources/Prototypes/Entities/Objects/Materials/materials.yml index c72e503b49..fde9aa692d 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/materials.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/materials.yml @@ -12,6 +12,7 @@ - type: Tag tags: - RawMaterial + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml index 78bbd32f17..99f9ccaa87 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml @@ -17,6 +17,9 @@ sound: /Audio/Weapons/star_hit.ogg - type: Stack count: 1 + - type: Tag + tags: + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 5fe88f8d0c..a5e08e9f54 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -65,6 +65,7 @@ solution: soap - type: DeleteOnSolutionEmpty solution: soap + - type: PaintRemover - type: FlavorProfile flavors: - clean diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index 37b2b03d9e..72b43296af 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -80,6 +80,9 @@ enabled: false reflectProb: 0.5 minReflectProb: 0.25 + - type: Tag + tags: + - NoPaint - type: IgnitionSource temperature: 700 @@ -171,6 +174,7 @@ - type: Tag tags: - Write + - NoPaint - type: DisarmMalus malus: 0 diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index cc69a6304d..0876502e67 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -31,6 +31,9 @@ sound: path: /Audio/Ambience/Objects/fireplace.ogg - type: AlwaysHot + - type: Tag + tags: + - NoPaint - type: entity id: LegionnaireBonfire diff --git a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml index f9bf81695e..7d5870bb01 100644 --- a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml +++ b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml @@ -25,6 +25,9 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag + tags: + - NoPaint - type: entity id: HoloFan diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 68a0cbd38e..82a19211fc 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -91,6 +91,9 @@ - type: GuideHelp guides: - Botany + - type: Tag + tags: + - NoPaint - type: entity parent: hydroponicsTray diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 6cb813ba80..e7f20f8f47 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -946,6 +946,9 @@ - type: Tag id: NoBlockAnchoring +- type: Tag + id: NoPaint + - type: Tag id: NozzleBackTank diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg b/Resources/Textures/Interface/VerbIcons/paint.svg new file mode 100644 index 0000000000..78a56e3570 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/paint.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png new file mode 100644 index 0000000000..b7bd88245f Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png differ diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png new file mode 100644 index 0000000000..d90d5b21b7 Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png new file mode 100644 index 0000000000..27b68e2cfe Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json index 0f883ee280..f34820cec4 100644 --- a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json @@ -58,9 +58,6 @@ { "name": "spray" }, - { - "name": "spray_cap" - }, { "name": "spray_cap_colors" }, @@ -70,6 +67,22 @@ { "name": "equipped-BELT", "directions": 4 + }, + { + "name": "clown-inhand-right", + "directions": 4 + }, + { + "name": "clown-inhand-left", + "directions": 4 + }, + { + "name": "spray-inhand-right", + "directions": 4 + }, + { + "name": "spray-inhand-left", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png new file mode 100644 index 0000000000..ad3ad959de Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png new file mode 100644 index 0000000000..353e47c56f Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png deleted file mode 100644 index 01d2d49bc0..0000000000 Binary files a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png and /dev/null differ diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 156e5d2701..cc2301558a 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -1,6 +1,14 @@  False False + HINT + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + WARNING WARNING WARNING WARNING @@ -34,24 +42,58 @@ WARNING WARNING WARNING - SUGGESTION - Required - Required - Required - Required - RequiredForMultiline - Required - Required - Required + WARNING + NotRequired + NotRequired + NotRequired + NotRequired + NotRequiredForBoth + NotRequired + NotRequired + NotRequired + ExpressionBody + Join + ExpressionBody + TargetTyped + True + False NEXT_LINE NEXT_LINE + False + False NEXT_LINE + 2 + 2 + 2 + 1 NEXT_LINE + TOGETHER_SAME_LINE + True + True + True + True + True + True + USUAL_INDENT + USUAL_INDENT INDENT NEXT_LINE NEXT_LINE + False + False + True + False + True NEXT_LINE + IF_OWNER_IS_SINGLE_LINE + NEVER + False NEXT_LINE + True + True + True + True + False AABB AL BB