mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 09:31:47 -05:00
Rework the Adrenaline system
This commit is contained in:
parent
fb06178b0b
commit
c97724967b
8 changed files with 242 additions and 85 deletions
|
@ -452,7 +452,7 @@ public class Typeface : Font
|
|||
if (fontSize <= 12)
|
||||
{
|
||||
// Disable anti-aliasing for low font sizes to make them less blurry.
|
||||
flags = FT_LOAD.FT_LOAD_MONOCHROME | FT_LOAD.FT_LOAD_FORCE_AUTOHINT;
|
||||
flags = FT_LOAD.FT_LOAD_MONOCHROME;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -204,7 +204,7 @@ namespace SociallyDistant.Core.Core.Scripting
|
|||
|
||||
if (GiveFlowBonuses)
|
||||
{
|
||||
var charactersTypedPerMinute = (int) Math.Floor(scriptText.Length / typingSlowness);
|
||||
var charactersTypedPerMinute = (int) Math.Floor(scriptText.Length / (typingSlowness / 60));
|
||||
this.potentialFlow += charactersTypedPerMinute;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ using AcidicGUI.VisualStyles;
|
|||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using SociallyDistant.Core.UI.Visuals;
|
||||
using SociallyDistant.Player;
|
||||
|
||||
namespace SociallyDistant.Applets;
|
||||
|
||||
|
@ -44,8 +45,10 @@ public sealed class FlowMeterWidget : Widget,
|
|||
this.InvalidateGeometry();
|
||||
}
|
||||
|
||||
public void SetAdrenaline(bool isInAdrenaline)
|
||||
public void SetAdrenaline(AdrenalineState adrenalineState)
|
||||
{
|
||||
var isInAdrenaline = adrenalineState == AdrenalineState.HighEnergy || adrenalineState == AdrenalineState.Adrenaline;
|
||||
|
||||
if (this.visualState.AdrenalineMode == isInAdrenaline)
|
||||
return;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Xna.Framework;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.TextRendering;
|
||||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.UI.Recycling.SettingsWidgets;
|
||||
using SociallyDistant.Player;
|
||||
using SociallyDistant.UI;
|
||||
|
@ -11,9 +13,15 @@ namespace SociallyDistant.Applets;
|
|||
internal sealed class FlowMeter : IStatusApplet
|
||||
{
|
||||
private readonly PlayerState playerSTate;
|
||||
private readonly FlowMeterWidget widget = new();
|
||||
private readonly StackPanel root = new();
|
||||
private readonly StackPanel indicators = new();
|
||||
private readonly TextWidget earnIndicator = new();
|
||||
private readonly TextWidget lossIndicator = new();
|
||||
private readonly FlowMeterWidget widget = new();
|
||||
private IDisposable? flowObserver;
|
||||
private IDisposable? adrenalineObserver;
|
||||
private IDisposable? lossObserver;
|
||||
private IDisposable? earnObserver;
|
||||
|
||||
|
||||
public IAppletSlot? CurrentSlot { get; set; }
|
||||
|
@ -21,15 +29,38 @@ internal sealed class FlowMeter : IStatusApplet
|
|||
|
||||
public FlowMeter(PlayerState playerSTate)
|
||||
{
|
||||
root.Direction = Direction.Horizontal;
|
||||
indicators.Direction = Direction.Vertical;
|
||||
|
||||
earnIndicator.TextColorOverride = Color.Cyan;
|
||||
lossIndicator.TextColorOverride = Color.Magenta;
|
||||
|
||||
earnIndicator.FontSize = 8;
|
||||
lossIndicator.FontSize = 8;
|
||||
|
||||
earnIndicator.FontFamily = PresetFontFamily.Monospace;
|
||||
lossIndicator.FontFamily = PresetFontFamily.Monospace;
|
||||
|
||||
root.Spacing = 3;
|
||||
indicators.MinimumWidth = 40;
|
||||
indicators.MaximumWidth = indicators.MinimumWidth;
|
||||
|
||||
root.ChildWidgets.Add(widget);
|
||||
root.ChildWidgets.Add(indicators);
|
||||
indicators.ChildWidgets.Add(earnIndicator);
|
||||
indicators.ChildWidgets.Add(lossIndicator);
|
||||
|
||||
this.playerSTate = playerSTate;
|
||||
}
|
||||
|
||||
public void Build(IAppletSlot slot)
|
||||
{
|
||||
this.CurrentSlot = slot;
|
||||
this.CurrentSlot.Content = widget;
|
||||
this.CurrentSlot.Content = root;
|
||||
flowObserver = playerSTate.FlowPercentageObservable.Subscribe(widget.SetFlow);
|
||||
adrenalineObserver = playerSTate.AdrenalineObservable.Subscribe(widget.SetAdrenaline);
|
||||
earnObserver = playerSTate.FlowEarnedObservable.Subscribe(OnFlowEarned);
|
||||
lossObserver = playerSTate.FlowLossObservable.Subscribe(OnFlowLost);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
|
@ -42,6 +73,24 @@ internal sealed class FlowMeter : IStatusApplet
|
|||
this.CurrentSlot = null;
|
||||
flowObserver?.Dispose();
|
||||
adrenalineObserver?.Dispose();
|
||||
earnObserver?.Dispose();
|
||||
lossObserver?.Dispose();
|
||||
}
|
||||
|
||||
private void OnFlowEarned(long amount)
|
||||
{
|
||||
this.earnIndicator.Text = $"+ {amount}";
|
||||
this.earnIndicator.RenderOpacity = amount == 0
|
||||
? 0
|
||||
: 1;
|
||||
}
|
||||
|
||||
private void OnFlowLost(long amount)
|
||||
{
|
||||
this.lossIndicator.Text = $"+ {amount}";
|
||||
this.lossIndicator.RenderOpacity = amount == 0
|
||||
? 0
|
||||
: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -142,9 +142,12 @@ namespace SociallyDistant.Core
|
|||
world.Wipe();
|
||||
}
|
||||
|
||||
internal void UpdateWorldClock()
|
||||
internal void UpdateWorldClock(bool adrenaline)
|
||||
{
|
||||
float clockDelta = Time.DeltaTime * World.GlobalWorldState.Value.TimeScale;
|
||||
if (adrenaline)
|
||||
clockDelta *= 0.5f;
|
||||
|
||||
if (clockDelta == 0)
|
||||
return;
|
||||
|
||||
|
|
|
@ -4,19 +4,39 @@ using SociallyDistant.Core.Core.Events;
|
|||
|
||||
namespace SociallyDistant.Player;
|
||||
|
||||
public enum AdrenalineState : byte
|
||||
{
|
||||
NoFlow,
|
||||
LowEnergy,
|
||||
Adrenaline,
|
||||
HighEnergy
|
||||
}
|
||||
|
||||
internal sealed class PlayerState
|
||||
{
|
||||
private readonly Subject<float> flowPercentageSubject = new();
|
||||
private readonly Subject<bool> flowStateSubject = new();
|
||||
private readonly uint flowNeededForAdrenaline = 100;
|
||||
private uint flow;
|
||||
private bool inPerfectFlow;
|
||||
private float timeLeftInAdrenaline = 0;
|
||||
private float timeSpentAfterAdrenaline;
|
||||
private float timeUntilNormalDecay = 0;
|
||||
private readonly Subject<float> flowPercentageSubject = new();
|
||||
private readonly Subject<AdrenalineState> flowStateSubject = new();
|
||||
private readonly Subject<long> flowLossSubject = new();
|
||||
private readonly Subject<long> flowEarnSibject = new();
|
||||
private const long flowNeededForAdrenaline = 1000;
|
||||
private const int flowPerSecond = 200;
|
||||
private long flowEarned;
|
||||
private long flowLost;
|
||||
private long flow;
|
||||
private AdrenalineState inPerfectFlow;
|
||||
private float timeLeftInAdrenaline = 0;
|
||||
private float timeSpentAfterAdrenaline;
|
||||
private float timeUntilNormalDecay = 0;
|
||||
private float timeSinceFlowEarn = 0;
|
||||
private float timeUntilAdrenaline;
|
||||
|
||||
public IObservable<bool> AdrenalineObservable => flowStateSubject;
|
||||
public IObservable<long> FlowEarnedObservable => flowEarnSibject;
|
||||
public IObservable<long> FlowLossObservable => flowLossSubject;
|
||||
public IObservable<AdrenalineState> AdrenalineObservable => flowStateSubject;
|
||||
public IObservable<float> FlowPercentageObservable => flowPercentageSubject;
|
||||
public bool IsHoppedUpOnAdrenaline => inPerfectFlow == AdrenalineState.Adrenaline || inPerfectFlow == AdrenalineState.HighEnergy;
|
||||
|
||||
public AdrenalineState Adrenaline => inPerfectFlow;
|
||||
|
||||
public PlayerState()
|
||||
{
|
||||
|
@ -28,99 +48,181 @@ internal sealed class PlayerState
|
|||
var amount = (uint) Math.Abs(flowEvent.FlowToAdd);
|
||||
var isPenalty = Math.Sign(flowEvent.FlowToAdd) == -1;
|
||||
|
||||
if (!isPenalty && this.inPerfectFlow)
|
||||
var isAdrenaline = inPerfectFlow == AdrenalineState.Adrenaline || inPerfectFlow == AdrenalineState.HighEnergy;
|
||||
|
||||
if (!isPenalty && isAdrenaline)
|
||||
amount *= 2;
|
||||
|
||||
if (isPenalty)
|
||||
{
|
||||
var newAmount = (long) flow - amount;
|
||||
if (newAmount < 0)
|
||||
{
|
||||
this.flow = 0;
|
||||
this.inPerfectFlow = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
flow = (uint)newAmount;
|
||||
}
|
||||
if (!isAdrenaline)
|
||||
flowLost += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
flow += amount;
|
||||
|
||||
timeUntilNormalDecay = 5;
|
||||
|
||||
if (!inPerfectFlow && flow >= flowNeededForAdrenaline)
|
||||
{
|
||||
timeLeftInAdrenaline = 60;
|
||||
inPerfectFlow = true;
|
||||
}
|
||||
flowEarned = Math.Min(flowEarned + amount, flowNeededForAdrenaline);
|
||||
}
|
||||
|
||||
SendUpdates();
|
||||
}
|
||||
|
||||
private void SendUpdates()
|
||||
{
|
||||
float flowPercentage = MathHelper.Clamp((float)flow / flowNeededForAdrenaline, 0, 1);
|
||||
|
||||
this.flowEarnSibject.OnNext(flowEarned);
|
||||
this.flowLossSubject.OnNext(flowLost);
|
||||
this.flowPercentageSubject.OnNext(flowPercentage);
|
||||
this.flowStateSubject.OnNext(inPerfectFlow);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (inPerfectFlow)
|
||||
switch (inPerfectFlow)
|
||||
{
|
||||
if (timeLeftInAdrenaline > 0)
|
||||
case AdrenalineState.Adrenaline:
|
||||
{
|
||||
timeSpentAfterAdrenaline = 0;
|
||||
timeLeftInAdrenaline -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (timeUntilAdrenaline <= 0)
|
||||
{
|
||||
inPerfectFlow = AdrenalineState.HighEnergy;
|
||||
break;
|
||||
}
|
||||
|
||||
// Once the adrenaline timer ends, we steadily decay flow until adrenaline actually
|
||||
// depletes. Every 10 seconds, the decay gets faster. This allows the player to keep
|
||||
// the adrenaline pumping for a while if they keep earning flow, but eventually it'll
|
||||
// always run out.
|
||||
timeSpentAfterAdrenaline += deltaTime / 10f;
|
||||
timeUntilAdrenaline -= deltaTime;
|
||||
break;
|
||||
}
|
||||
case AdrenalineState.HighEnergy:
|
||||
{
|
||||
// You cannot lose flow when in Adrenaline.
|
||||
flowLost = 0;
|
||||
|
||||
var newFlow = (long)flow - (int) Math.Floor(timeSpentAfterAdrenaline);
|
||||
if (newFlow <= 0)
|
||||
{
|
||||
flow = 0;
|
||||
inPerfectFlow = false;
|
||||
timeSpentAfterAdrenaline = 0;
|
||||
// Any flow earned while in Adrenaline gets doubled an extends Adrenaline.
|
||||
if (flowEarned > 0)
|
||||
{
|
||||
timeSinceFlowEarn += deltaTime * flowPerSecond * 2;
|
||||
if (timeSinceFlowEarn >= 1)
|
||||
{
|
||||
int intervalsPassed = (int) Math.Floor(timeSinceFlowEarn);
|
||||
long flowEarnedNow = Math.Min(flowEarned, intervalsPassed);
|
||||
timeSinceFlowEarn -= intervalsPassed;
|
||||
|
||||
SendUpdates();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
flow = (uint)newFlow;
|
||||
SendUpdates();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timeUntilNormalDecay > 0)
|
||||
{
|
||||
timeUntilNormalDecay -= deltaTime;
|
||||
return;
|
||||
}
|
||||
flowEarned -= flowEarnedNow;
|
||||
flow += flowEarnedNow;
|
||||
|
||||
// Decay at a rate of 5 per second
|
||||
var newFlow = (long)flow - (int)Math.Ceiling(deltaTime * 5);
|
||||
if (newFlow <= 0)
|
||||
{
|
||||
flow = 0;
|
||||
SendUpdates();
|
||||
return;
|
||||
// Only extend Adrenaline if we still have time left. Otherwise, the flow increase will give a small boost
|
||||
// even though Adrenaline is depleted.
|
||||
if (timeLeftInAdrenaline > 0)
|
||||
timeLeftInAdrenaline += ((float)flowEarnedNow / (float)flowPerSecond);
|
||||
|
||||
SendUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
if (timeLeftInAdrenaline > 0)
|
||||
{
|
||||
timeSpentAfterAdrenaline = 0;
|
||||
timeLeftInAdrenaline -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// Once the adrenaline timer ends, we steadily decay flow until adrenaline actually
|
||||
// depletes. Every 10 seconds, the decay gets faster. This allows the player to keep
|
||||
// the adrenaline pumping for a while if they keep earning flow, but eventually it'll
|
||||
// always run out.
|
||||
timeSpentAfterAdrenaline += deltaTime / 10f;
|
||||
|
||||
var newFlow = (long)flow - (int)Math.Floor(timeSpentAfterAdrenaline);
|
||||
if (newFlow <= 0)
|
||||
{
|
||||
flow = 0;
|
||||
inPerfectFlow = AdrenalineState.NoFlow;
|
||||
timeSpentAfterAdrenaline = 0;
|
||||
|
||||
SendUpdates();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
flow = (uint)newFlow;
|
||||
SendUpdates();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
default:
|
||||
{
|
||||
flow = (uint)newFlow;
|
||||
SendUpdates();
|
||||
if (flowLost > 0)
|
||||
{
|
||||
if (flowEarned > 0)
|
||||
{
|
||||
long newBalance = flowEarned - flowLost;
|
||||
|
||||
flowEarned = Math.Max(flowEarned - flowLost, 0);
|
||||
|
||||
long debt = Math.Abs(flowEarned - newBalance);
|
||||
flowLost = debt;
|
||||
}
|
||||
|
||||
var flowLostNow = (long)Math.Floor((double)flowLost * (deltaTime * flowPerSecond));
|
||||
if (flowLostNow <= 0)
|
||||
flowLostNow = flowLost;
|
||||
|
||||
flowLost -= flowLostNow;
|
||||
flow -= flowLostNow;
|
||||
|
||||
if (flow < 0)
|
||||
{
|
||||
flow = 0;
|
||||
flowLost = 0;
|
||||
}
|
||||
|
||||
SendUpdates();
|
||||
}
|
||||
|
||||
if (flowEarned > 0)
|
||||
{
|
||||
timeSinceFlowEarn += deltaTime * flowPerSecond;
|
||||
if (timeSinceFlowEarn >= 1)
|
||||
{
|
||||
int intervalsPassed = (int) Math.Floor(timeSinceFlowEarn);
|
||||
long flowEarnedNow = Math.Min(flowEarned, intervalsPassed);
|
||||
timeSinceFlowEarn -= intervalsPassed;
|
||||
|
||||
flowEarned -= flowEarnedNow;
|
||||
flow += flowEarnedNow;
|
||||
|
||||
if (flow >= flowNeededForAdrenaline && inPerfectFlow != AdrenalineState.Adrenaline)
|
||||
{
|
||||
timeUntilAdrenaline = 1;
|
||||
inPerfectFlow = AdrenalineState.Adrenaline;
|
||||
timeSpentAfterAdrenaline = 0;
|
||||
timeLeftInAdrenaline = 60;
|
||||
}
|
||||
|
||||
timeUntilNormalDecay = 5;
|
||||
SendUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
if (timeUntilNormalDecay > 0)
|
||||
{
|
||||
timeUntilNormalDecay -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// Decay at a rate of 5 per second
|
||||
var newFlow = (long)flow - (int)Math.Ceiling(deltaTime * 5);
|
||||
if (newFlow <= 0)
|
||||
{
|
||||
flow = 0;
|
||||
SendUpdates();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
flow = (uint)newFlow;
|
||||
SendUpdates();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -562,7 +562,7 @@ internal sealed class SociallyDistantGame :
|
|||
{
|
||||
debugOverlay.UpdateKeyboard();
|
||||
|
||||
worldManager.UpdateWorldClock();
|
||||
worldManager.UpdateWorldClock(playerManager.PlayerStateInternal.IsHoppedUpOnAdrenaline);
|
||||
virtualScreen?.Activate();
|
||||
|
||||
// Run any scheduled actions
|
||||
|
|
|
@ -150,9 +150,9 @@ public class GuiController : GameComponent,
|
|||
{
|
||||
statusApplets.Add(userApplet);
|
||||
statusApplets.Add(new WorkspaceSwitcher(this, floatingWindowArea));
|
||||
statusApplets.Add(new SpacerApplet());
|
||||
statusApplets.Add(new FlowMeter(playerManager.PlayerStateInternal));
|
||||
|
||||
statusApplets.Add(new SpacerApplet());
|
||||
statusApplets.Add(new SpacerApplet());
|
||||
statusApplets.Add(new SystemTrayApplet(trayModel));
|
||||
|
||||
|
|
Loading…
Reference in a new issue