Skip to content

Instantly share code, notes, and snippets.

@spajus
Created December 9, 2023 06:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spajus/f3cc31ad883f4182f0e8e133ffbcb298 to your computer and use it in GitHub Desktop.
Save spajus/f3cc31ad883f4182f0e8e133ffbcb298 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Generic;
using Game.Audio;
using Game.Commands;
using Game.Components;
using Game.Constants;
using Game.Data;
using Game.Utils;
using Game.Visuals;
using KL.Clock;
using KL.Grid;
using KL.Randomness;
using KL.Utils;
using Unity.Mathematics;
using UnityEngine;
namespace Game.Story.Events {
public sealed class MicroMeteorStrikeStoryEvent : StoryEvent {
public const string Id = "MicroMeteorStrike";
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType
.SubsystemRegistration)]
private static void Register() {
Register(meta);
}
private static readonly StoryEventMeta meta = new() {
Id = Id,
Create = () => new MicroMeteorStrikeStoryEvent(),
};
public override StoryEventMeta Meta => meta;
public override EventLogEntry LogEntry => BuildLogEntry();
public override float TensionToAddOnStart => 0.5f;
private EventLogEntry logEntry;
private Facing.Type directionFrom;
private EventLogEntry BuildLogEntry() => logEntry ??= EventLogEntry.CreateFor(this,
T.EvtMicroMeteorsTitle, T.EvtMicroMeteorsDesc, IconId.CWarning);
public override void AfterEnqueue() {
var rng = S.Rng;
directionFrom = rng.From(Facing.Types);
}
private long nextHitTick;
private int hitCount;
public override void OnTick(long ticks) {
if (ticks > nextHitTick) {
EnsureWarningEvent();
Hit(ticks);
}
}
private readonly HashSet<int> hitObjects = new();
private void Hit(long ticks) {
hitObjects.Clear();
var count = Pos.GridW / 32;
var posF = EntityUtils.MeteorVector(directionFrom, 10f, count, Rng.Unseeded,
out var strikeV);
var startPos = posF(Mathf.RoundToInt(S.Rng.Range(count * 0.33f, count * 0.66f)));
var wt = WantedTension;
Vector2 curPos = startPos;
var stepV = strikeV.normalized;
if (stepV.sqrMagnitude < 0.001f) {
D.Err("stepV is zero!");
} else {
var healthLeft = S.Rng.Range(200f, 300f) * wt / 5f;
healthLeft = math.clamp(healthLeft, 200f, 1000f);
bool hit = false;
var stepsLeft = Pos.GridW * 2f;
var a = S.Sys.Areas;
while (healthLeft > 0 && stepsLeft-- > 0) {
curPos += stepV;
var p = EntityUtils.ToPos(curPos);
if (Pos.IsOOB(p)) { continue; }
if (S.Sys.Sec.IsInForceFieldRange(curPos)) {
hit = true;
CmdCreateExplosion.CreateExplosionFX(S, curPos, 2f, 200f);
break;
}
if (S.Beings.ByPos.TryGetValue(p, out var hitBeings)) {
for (int i = hitBeings.Count - 1; i >= 0; i--) {
Being b = hitBeings[i];
var beingDmg = Mathf.Min(healthLeft, b.Health.Health);
b.Health.TakeDamage(beingDmg, DamageType.Physical);
healthLeft -= beingDmg * 0.5f;
hit = true;
}
}
var wall = EntityUtils.WallAt(p);
if (wall?.IsConstructed == true) {
var wallH = wall.Damageable.Health;
if (S.Rng.Chance(0.7f)) {
wallH *= S.Rng.Range(0.85f, 0.99f);
}
var dmgAmt = Mathf.Min(healthLeft, wallH);
wall.Damageable.TakeDamage(dmgAmt, DamageType.Physical);
healthLeft -= dmgAmt;
hit = true;
CmdCreateExplosion.CreateExplosionFX(S, curPos, 2f, dmgAmt);
}
var obj = EntityUtils.ParentObjAt(p);
if (obj?.IsConstructed == true && hitObjects.Add(obj.PosIdx)
&& !obj.IsImmutable) {
// We hit the multi-tile object for the first time
var objH = obj.Damageable.Health;
if (S.Rng.Chance(0.7f)) {
objH *= S.Rng.Range(0.85f, 0.99f);
}
var dmgAmt = Mathf.Min(healthLeft, obj.Damageable.Health);
obj.Damageable.TakeDamage(dmgAmt, DamageType.Physical);
healthLeft -= dmgAmt;
CmdCreateExplosion.CreateExplosionFX(S, curPos, 2f, dmgAmt);
hit = true;
}
}
if (hit) {
S.Situation.AddTension(0.33f, "Micro Meteor: Hit");
hitCount++;
} else {
S.Situation.AddTension(0.15f, "Micro Meteor: Miss");
}
}
Coroutines.Start(ShowLine(ticks, Consts.TicksPer5Minutes,
startPos, curPos), this, "ShowLine");
if (hitCount >= 5 && wt < 0.1f
|| S.Rng.Chance(Mathf.Clamp(0.01f, 0.1f, 1f / wt))) {
EndEvent();
} else {
nextHitTick = ticks + Rng.URange(Consts.TicksPer3Minutes,
Consts.TicksPer15Minutes);
}
}
private IEnumerator ShowLine(long start, int durationInTicks,
Vector2 startPos, Vector2 endPos) {
var line = Lines.Create("BulletTrailLineFX", startPos, endPos);
var alphaStart = 0.25f;
var alphaEnd = 0.75f;
var endTick = start + durationInTicks;
while (S.Ticks < endTick) {
var alphaAmount = Mathf.InverseLerp(endTick, start, S.Ticks);
line.SetAlpha(alphaStart * alphaAmount, alphaEnd * alphaAmount);
yield return null;
if (!GameState.IsCurrent(S)) { yield break; }
}
line.Destroy();
}
private EventNotification warningEvent;
public override void OnStart(long ticks) {
new CmdRestrictClock(
ClockSpeed.Normal, Consts.TicksPer10Minutes).Execute(S);
Sounds.PlayMusic(SoundtrackMood.Danger, true);
}
private void EnsureWarningEvent() {
if (warningEvent == null) {
var evt = UDB.Create(
"story", UDBT.IEventSticky,
IconId.CWarning, T.EvtMicroMeteorsTitle)
.WithIconClickFunction(LogEntry.ShowDetails);
warningEvent = EventNotification.Create(S.Ticks,
evt, Priority.Critical, true)
.WithUniqueId("micrometeors")
.AsEmergency(false, true);
S.Sig.AddEvent.Send(warningEvent);
}
}
protected override void OnLoad(ComponentData data) {
nextHitTick = data.GetLong("NextHit", 0);
directionFrom = (Facing.Type) data.GetInt("Direction", 0);
}
protected override void OnSave(ComponentData data) {
data.SetLong("NextHit", nextHitTick);
data.SetInt("Direction", (int) directionFrom);
}
public override void OnEnd(long ticks) {
if (warningEvent != null) {
S.Sig.RemoveEvent.Send(warningEvent);
warningEvent = null;
}
S.Sig.ShowFanfare.Send(Texts.Green(T.EvtMicroMeteorsEnded));
S.Sys.Log.AddLine(T.EvtMicroMeteorsEnded, Consts.ColorTextGreen);
Sounds.PlayMusic(SoundtrackMood.Calm, false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment