Skip to content

Commit

Permalink
Merge pull request #79 from machination-of-ruin/lights
Browse files Browse the repository at this point in the history
Zavoz Kontenta
  • Loading branch information
Vaaankas authored Nov 7, 2024
2 parents 99e4f16 + 53c38a7 commit d1beaae
Show file tree
Hide file tree
Showing 22 changed files with 555 additions and 19 deletions.
142 changes: 142 additions & 0 deletions Content.Client/_ERRORGATE/DayCycle/LightCycleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Content.Client.GameTicking.Managers;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using LightCycleComponent = Content.Shared._ERRORGATE.DayCycle.LightCycleComponent;

namespace Content.Client._ERRORGATE.DayCycle
{
public sealed partial class LightCycleSystem : EntitySystem
{
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<LightCycleComponent, ComponentShutdown>(OnComponentShutdown);
}

private void OnComponentShutdown(EntityUid uid, LightCycleComponent cycle, ComponentShutdown args)
{
if (LifeStage(uid) >= EntityLifeStage.Terminating)
return;
if (_entityManager.TryGetComponent<MapLightComponent>(uid, out var map))
{
map.AmbientLightColor = Color.FromHex(cycle.OriginalColor);
}
}

public override void FrameUpdate(float frameTime)
{
var mapQuery = EntityQueryEnumerator<MapLightComponent, LightCycleComponent>();
while (mapQuery.MoveNext(out var uid, out var map, out var cycle))
{
if (cycle.OriginalColor != null && !cycle.OriginalColor.ToUpper().Equals("#0000FF"))
{
var time = _gameTiming.CurTime.Subtract(cycle.Offset).Subtract(_gameTicker.RoundStartTimeSpan).TotalSeconds + cycle.InitialTime;
var color = GetColor((uid, cycle), Color.FromHex(cycle.OriginalColor), time);
if (!color.Equals(map.AmbientLightColor.ToHex()))
{
map.AmbientLightColor = color;
}
}
else
{
cycle.OriginalColor = map.AmbientLightColor.ToHex();
}
}
}

// Decomposes the color into its components and multiplies each one by the individual color level as function of time, returning a new color.
public static Color GetColor(Entity<LightCycleComponent> cycle, Color color, double time)
{
if (cycle.Comp.IsEnabled)
{
var lightLevel = CalculateLightLevel(cycle.Comp, time);
var colorLevel = CalculateColorLevel(cycle.Comp, time);
var rgb = new int[] { (int) Math.Min(255, color.RByte * colorLevel[0] * lightLevel),
(int) Math.Min(255, color.GByte * colorLevel[1] * lightLevel),
(int) Math.Min(255, color.BByte * colorLevel[2] * lightLevel) };
var hex = "#";
for (int i = 0, j = 0; i < 6; i++)
{
if (i % 2 == 0)
{
hex += (rgb[j] / 16).ToString("X");
}
else
{
hex += (rgb[j] % 16).ToString("X");
j++;
}
}
return Color.FromHex(hex);
}
else
return color;

}

// Calculates light intensity as a function of time.

public static double CalculateLightLevel(LightCycleComponent comp, double time)
{
var wave_lenght = Math.Max(1, comp.CycleDuration);
var crest = Math.Max(0, comp.MaxLightLevel);
var shift = Math.Max(0, comp.MinLightLevel);
return Math.Min(comp.ClipLight, CalculateCurve(time, wave_lenght, crest, shift, 6));
}

/// <summary>
/// Returns a double vector with color levels, where 0 = Red, 1 = Green, 2 = Blue.
/// It is important to note that each color must have a different exponent, to modify how early or late one color should stand out in relation to another.
/// This "simulates" what the atmosphere does and is what generates the effect of dawn and dusk.
/// The blue component must be a cosine function with half period, so that its minimum is at dawn and dusk, generating the "warm" color corresponding to these periods.
/// As you can see in the values, the maximums of the function serve more to define the curve behavior,
/// they must be "clipped" so as not to distort the original color of the lighting. In practice, the maximum values, in fact, are the clip thresholds.
/// </summary>

public static double[] CalculateColorLevel(LightCycleComponent comp, double time)
{
var wave_lenght = Math.Max(1, comp.CycleDuration);
var color_level = new double[3];
for (var i = 0; i < 3; i++)
{
switch (i)
{
case 0:
color_level[i] = Math.Min(comp.ClipRed, CalculateCurve(time, wave_lenght,
Math.Max(0, comp.MaxRedLevel), Math.Max(0, comp.MinRedLevel), 4));
break;
case 1:
color_level[i] = Math.Min(comp.ClipGreen, CalculateCurve(time, wave_lenght,
Math.Max(0, comp.MaxGreenLevel), Math.Max(0, comp.MinGreenLevel), 10));
break;
case 2:
color_level[i] = Math.Min(comp.ClipBlue, CalculateCurve(time, wave_lenght / 2,
Math.Max(0, comp.MaxBlueLevel), Math.Max(0, comp.MinBlueLevel), 2, wave_lenght / 4));
break;
}
}
return color_level;
}

/// <summary>
/// Generates a sinusoidal curve as a function of x (time). The other parameters serve to adjust the behavior of the curve.
/// </summary>
/// <param name="x"> It corresponds to the independent variable of the function, which in the context of this algorithm is the current time. </param>
/// <param name="wave_lenght"> It's the wavelength of the function, it can be said to be the total duration of the light cycle. </param>
/// <param name="crest"> It's the maximum point of the function, where it will have its greatest value. </param>
/// <param name="shift"> It's the vertical displacement of the function, in practice it corresponds to the minimum value of the function. </param>
/// <param name="exponent"> It is the exponent of the sine, serves to "flatten" the function close to its minimum points and make it "steeper" close to its maximum. </param>
/// <param name="phase"> It changes the phase of the wave, like a "horizontal shift". It is important to transform the sinusoidal function into cosine, when necessary. </param>
/// <returns> The result of the function. </returns>

public static double CalculateCurve(double x, double wave_lenght, double crest, double shift, double exponent, double phase = 0)
{
var sen = Math.Pow(Math.Sin((Math.PI * (phase + x)) / wave_lenght), exponent);
return (crest - shift) * sen + shift;
}
}
}
23 changes: 23 additions & 0 deletions Content.Server/_ERRORGATE/DayCycle/LightCycleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Server.GameTicking;
using LightCycleComponent = Content.Shared._ERRORGATE.DayCycle.LightCycleComponent;

namespace Content.Server._ERRORGATE.DayCycle
{
public sealed partial class LightCycleSystem : EntitySystem
{
[Dependency] private readonly GameTicker _gameTicker = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<LightCycleComponent, ComponentStartup>(OnComponentStartup);
}

private void OnComponentStartup(EntityUid uid, LightCycleComponent cycle, ComponentStartup args)
{
cycle.Offset = _gameTicker.RoundDuration();
}

}

}
7 changes: 5 additions & 2 deletions Content.Shared/Wieldable/WieldableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.IdentityManagement;
using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Timing;
Expand Down Expand Up @@ -198,7 +199,7 @@ public bool TryWield(EntityUid used, WieldableComponent component, EntityUid use
return false;

var selfMessage = Loc.GetString("wieldable-component-successful-wield", ("item", used));
var othersMessage = Loc.GetString("wieldable-component-successful-wield-other", ("user", user), ("item", used));
var othersMessage = Loc.GetString("wieldable-component-successful-wield-other", ("user", Identity.Entity(user, EntityManager)), ("item", used));
_popupSystem.PopupPredicted(selfMessage, othersMessage, user, user);

var targEv = new ItemWieldedEvent();
Expand Down Expand Up @@ -232,6 +233,8 @@ private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUn
if (args.User == null)
return;

var user = (EntityUid) args.User;

if (TryComp<ItemComponent>(uid, out var item))
{
_itemSystem.SetHeldPrefix(uid, component.OldInhandPrefix, component: item);
Expand All @@ -243,7 +246,7 @@ private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUn
_audioSystem.PlayPredicted(component.UnwieldSound, uid, args.User);

var selfMessage = Loc.GetString("wieldable-component-failed-wield", ("item", uid));
var othersMessage = Loc.GetString("wieldable-component-failed-wield-other", ("user", args.User.Value), ("item", uid));
var othersMessage = Loc.GetString("wieldable-component-failed-wield-other", ("user", Identity.Entity(user, EntityManager)), ("item", uid));
_popupSystem.PopupPredicted(selfMessage, othersMessage, args.User.Value, args.User.Value);
}

Expand Down
62 changes: 62 additions & 0 deletions Content.Shared/_ERRORGATE/DayCycle/LightCycleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Robust.Shared.GameStates;

namespace Content.Shared._ERRORGATE.DayCycle
{
[NetworkedComponent, RegisterComponent]
[AutoGenerateComponentState]
public sealed partial class LightCycleComponent : Component
{

public string? OriginalColor;

[AutoNetworkedField]
[DataField("offset")]
public TimeSpan Offset;

[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("isEnabled")]
public bool IsEnabled = true;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("initialTime")]
public int InitialTime = 600;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("cycleDuration")]
public int CycleDuration = 1800;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("minLightLevel")]
public double MinLightLevel = 0.2;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("maxLightLevel")]
public double MaxLightLevel = 1.25;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("clipLight")]
public double ClipLight = 1.25;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("clipRed")]
public double ClipRed = 1;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("clipGreen")]
public double ClipGreen = 1;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("clipBlue")]
public double ClipBlue = 1.25;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("minRedLevel")]
public double MinRedLevel = 0.1;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("minGreenLevel")]
public double MinGreenLevel = 0.15;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("minBlueLevel")]
public double MinBlueLevel = 0.50;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("maxRedLevel")]
public double MaxRedLevel = 2;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("maxGreenLevel")]
public double MaxGreenLevel = 2;
[AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite), DataField("maxBlueLevel")]
public double MaxBlueLevel = 5;
}
}
2 changes: 1 addition & 1 deletion Resources/Prototypes/Entities/Clothing/Head/bandanas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
enabled: false
- type: IdentityBlocker
enabled: false
coverage: MOUTH
coverage: FULL
- type: Sprite # needed for vendor inventory icons
layers:
- state: icon
Expand Down
2 changes: 1 addition & 1 deletion Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- type: Mask
- type: IngestionBlocker
- type: IdentityBlocker
coverage: MOUTH
coverage: FULL
- type: Sprite
layers:
- state: icon_mask
Expand Down
4 changes: 2 additions & 2 deletions Resources/Prototypes/Entities/Clothing/Masks/masks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@
- type: Item
storedRotation: -90
- type: IdentityBlocker
coverage: MOUTH
coverage: FULL

- type: entity
parent: ClothingMaskBase
Expand Down Expand Up @@ -511,7 +511,7 @@
- type: Clothing
sprite: Clothing/Mask/neckgaiter.rsi
- type: IdentityBlocker
coverage: MOUTH
coverage: FULL
- type: Tag
tags:
- WhitelistChameleon
Expand Down
5 changes: 5 additions & 0 deletions Resources/Prototypes/Entities/Mobs/Species/human.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
abstract: true
components:
- type: Hunger
baseDecayRate: 0.1
starvationDamage:
types:
Bloodloss: 1
- type: Icon # It will not have an icon in the adminspawn menu without this. Body parts seem fine for whatever reason.
sprite: Mobs/Species/Human/parts.rsi
state: full
- type: Thirst
baseDecayRate: 0.1
- type: Carriable # Carrying system from nyanotrasen.
- type: Butcherable
butcheringType: Knife
Expand Down
8 changes: 4 additions & 4 deletions Resources/Prototypes/Entities/Mobs/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,10 @@
heatDamage:
types:
Heat: 1.5 #per second, scales with temperature & other constants
- type: Barotrauma
damage:
types:
Blunt: 0.15 #per second, scales with pressure and other constants.
#- type: Barotrauma
# damage:
# types:
# Blunt: 0.15 #per second, scales with pressure and other constants.

# Used for mobs that can be set on fire
- type: entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
- type: TimerTriggerVisuals
primingSound:
path: "/Audio/Effects/packetrip.ogg"
- type: Tag
tags:
- Grenade

- type: Tag
id: Grenade

- type: entity
name: flashbang
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
closeSound:
path: /Audio/Effects/stonedoor_openclose.ogg
- type: Appearance
- type: Airtight
#- type: Airtight
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
- type: Storage
grid:
- 0,0,6,3
maxItemSize: Normal
maxItemSize: Large
- type: ContainerContainer
containers:
storagebase: !type:Container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@
- type: lootEntry
id: WeaponPistolGlock
rarityTiers: [2]
locations: [Living, Security, Military]
locations: [Living, Security, Military, Prison]
childEntries: [Magazine9x19]

- type: lootEntry
id: WeaponPistolStechkin
rarityTiers: [2]
locations: [Prison, Living]
locations: [Military, Security]
childEntries: [Magazine9x19HighCapacity]

- type: lootEntry
Expand Down
Loading

0 comments on commit d1beaae

Please sign in to comment.