HeavenStudioPlus/Assets/Scripts/Games/FirstContact/FirstContact.cs
Zeo e50e4e39e0
Mass Text Update (#615)
* Air Rally Text Update

* Blue Bear Text Update

* Board Meeting Text Update

* Built To Scale DS Text Update

also changed Air Rally's assetbundle tag from "normal" to "keep"

* Catchy Tune Text Update

also changed some minor wording in Board Meeting and Built To Scale DS

* Cheer Readers Text Update

* The Clappy Trio Text Update

* Coin Toss Text Update

* Crop Stomp Text Update

* DJ School Text Update

* Dog Ninja Text Update

* Double Date Text Update

* Drumming Practice Text Update

* Fan Club Text Update

* Fireworks Text Update

* Second Contact Text Update

* Flipper-Flop Text Update

also fix an error in Catchy Tune

* Fork Lifter Text Update

* Glee Club Text Update

* Karate Man Text Update

also minor updates to other games

* Kitties! Text Update

* Launch Party Text Update

* Lockstep Text Update

* Marching Orders Text Update

* Meat Grinder Text Update

also fixed an error in Second Contact

* Mr. Upbeat Text Update

* Munchy Monk Text Update

* Octopus Machine Text Update

* Pajama Party Text Update

* Quiz Show Text Update

also changed some wording in meat grinder

* Rhythm Rally Text Update

* Rhythm Somen Text Update

that was easy

* Rhythm Tweezers Text Update

* Ringside Text Update

* Rockers Text Update

this sucked

* Samurai Slice DS Text Update

* See Saw Text Update

* Sneaky Spirits Text Update

* Spaceball Text Update

* Space Dance Text Update

* Space Soccer Text Update

* Splashdown Text Update

* Tambourine Text Update

* Tap Trial Text Update

* Tap Troupe Text Update

* The Dazzles Text Update

* Toss Boys Text Update

* Tram & Pauline Text Update

also added translation for Fireworks

* Tunnel Text Update

* Wizard's Waltz Text Update

* Working Dough Text Update

* fix compiler errors

* fix editor offset bug(?)

* fix missing param in second contact

* Ball Redispense text

* remove space soccer swing

* Trick on the Class Text Update

* Non-Game Text Update

* fix pre-function sorting

* camera shake ease

* remove a bunch of prints

* rhythm tweezers bug fix

* Update Credits.txt

* ssds nop samurai bop

* swap order of shake properties

* Update FirstContact.cs

---------

Co-authored-by: minenice55 <star.elementa@gmail.com>
2024-01-15 02:04:10 +00:00

652 lines
No EOL
24 KiB
C#

using System;
using System.Text;
using System.Collections.Generic;
using UnityEngine;
using HeavenStudio.Util;
using HeavenStudio.InputSystem;
using TMPro;
using Jukebox;
using UnityEngine.Assertions.Must;
using UnityEngine.UIElements;
namespace HeavenStudio.Games.Loaders
{
using static Minigames;
public static class CtrFirstContact
{
public static Minigame AddGame(EventCaller eventCaller)
{
return new Minigame("firstContact", "Second Contact", "1f3833", false, false, new List<GameAction>()
{
new GameAction("beat intervals", "Start Interval")
{
preFunction = delegate { var e = eventCaller.currentEntity; FirstContact.PreInterval(e.beat, e.length, e["dialogue"], e["auto"]); },
parameters = new List<Param>()
{
new Param("dialogue", "REPLACE THIS", "Mistranslation Dialogue", "Set the line to use when messing up the translation."),
new Param("auto", true, "Auto Pass Turn", "Toggle if the turn should be passed automatically at the end of the start interval.")
},
defaultLength = 3f,
resizable = true,
priority = 2,
},
new GameAction("alien speak", "Bob Speak")
{
defaultLength = 0.5f,
parameters = new List<Param>()
{
new Param("spaceNum", new EntityTypes.Integer(0, 12, 0), "Amount Of Spaces", "Choose the amount of spaces to add before the icon."),
new Param("dotdotdot", false, "Ellipses", "Toggle if the symbol should have \"...\" before it."),
new Param("newline", false, "New line", "Toggle if this text starts a new line."),
new Param("dialogue", "", "Translation", "Set the text that this syllable will translate to. Spaces will not be automatically added.")
},
priority = 1
},
new GameAction("alien turnover", "Pass Turn")
{
function = delegate { FirstContact.instance.PassTurnStandalone(eventCaller.currentEntity.beat); },
resizable = true,
},
new GameAction("alien success", "Success")
{
function = delegate { FirstContact.instance.AlienSuccess(eventCaller.currentEntity.beat); },
},
new GameAction("mission control", "Show Mission Control")
{
function = delegate { var e = eventCaller.currentEntity; FirstContact.instance.MissionControlDisplay(e.beat, e["toggle"], e.length); },
resizable = true,
parameters = new List<Param>
{
new Param("toggle", false, "Stay", "Toggle if the camera should stay on mission control even after the event has ended. This is usually used for the end of a level.")
}
},
new GameAction("look at", "Look")
{
function = delegate { FirstContact.instance.LookAtDirection(eventCaller.currentEntity["type"], eventCaller.currentEntity["type2"]); },
defaultLength = .5f,
parameters = new List<Param>()
{
new Param("type", FirstContact.alienLookAt.lookAtAlien, "Farmer Bob", "Set where Farmer Bob will look."),
new Param("type2", FirstContact.translatorLookAt.lookAtBob, "Alien", "Set where the alien will look."),
}
},
new GameAction("live bar beat", "\"Live\" Bar")
{
function = delegate { FirstContact.instance.LiveBarBeat(eventCaller.currentEntity["toggle"]); },
defaultLength = .5f,
parameters = new List<Param>()
{
new Param("toggle", true, "On Beat", "Toggle if the \"Live\" bar in the top-left will animate on onbeats or offbeats.")
}
}
},
new List<string>() { "ctr", "repeat" },
"ctrinterpreter", "en",
new List<string>() { }
);
}
}
}
namespace HeavenStudio.Games
{
//using Scripts_FirstContact;
public class FirstContact : Minigame
{
const string MID_MSG_MISS = "<color=\"red\"> ..? </color>";
const string MSG_ALIEN = "<sprite name=\"AlienIcn\">";
const string MSG_MAN = "<sprite name=\"ManIcn\">";
const string MSG_MAN_DDD = "...<sprite name=\"ManIcn\">";
// I should add a DonkTroll sprite 🫰🫰🫰🫰🫰
public static FirstContact instance { get; private set; }
//[Header("Properties")]
private bool hasMissed;
private double lastReportedBeat = 0;
[Header("Components")]
[SerializeField] Animator alien;
[SerializeField] Animator translator;
//[SerializeField] GameObject alienSpeech;
[SerializeField] GameObject missionControl;
[SerializeField] Animator liveBar;
[SerializeField] GameObject alienTextbox;
[SerializeField] TMP_Text alienText;
[SerializeField] GameObject translateTextbox;
[SerializeField] TMP_Text translateText;
[SerializeField] GameObject translateFailTextbox;
[SerializeField] TMP_Text translateFailText;
[Header("Variables")]
int currentVoicelineIndex = -1;
public bool noHitOnce, isSpeaking;
//public int version;
public float lookAtLength = 1f;
bool onBeat;
float liveBarBeatOffset;
string onOutDialogue = "YOU SUCK AT CHARTING";
string callDiagBuffer = "";
string respDiagBuffer = "";
List<(string, bool)> callDiagList = new();
int callDiagIndex = 0;
private struct QueuedInterval
{
public double beat;
public float interval;
public bool autoPassTurn;
public string outDialogue;
}
private static List<QueuedInterval> queuedIntervals = new List<QueuedInterval>();
//public enum VersionOfContact
//{
// FirstContact,
// CitrusRemix,
// SecondContact
//}
public enum alienLookAt
{
lookAtAlien,
idle
}
public enum translatorLookAt
{
lookAtBob,
idle
}
protected static bool IA_PadAny(out double dt)
{
return PlayerInput.GetPadDown(InputController.ActionsPad.East, out dt)
|| PlayerInput.GetPadDown(InputController.ActionsPad.Up, out dt)
|| PlayerInput.GetPadDown(InputController.ActionsPad.Down, out dt)
|| PlayerInput.GetPadDown(InputController.ActionsPad.Left, out dt)
|| PlayerInput.GetPadDown(InputController.ActionsPad.Right, out dt);
}
public static PlayerInput.InputAction InputAction_Press =
new("CtrInterpreterPress", new int[] { IAPressCat, IAFlickCat, IAPressCat },
IA_PadAny, IA_TouchBasicPress, IA_BatonBasicPress);
void OnDestroy()
{
foreach (var evt in scheduledInputs)
{
evt.Disable();
}
}
private void Awake()
{
instance = this;
}
private void Start()
{
callDiagBuffer = "";
respDiagBuffer = "";
callDiagList.Clear();
callDiagIndex = 0;
alienTextbox.SetActive(false);
translateTextbox.SetActive(false);
translateFailTextbox.SetActive(false);
}
private List<RiqEntity> GetAllSpeaksInBetweenBeat(double beat, double endBeat)
{
List<RiqEntity> speakEvents = EventCaller.GetAllInGameManagerList("firstContact", new string[] { "alien speak" });
List<RiqEntity> tempEvents = new();
foreach (var entity in speakEvents)
{
if (entity.beat >= beat && entity.beat < endBeat)
{
tempEvents.Add(entity);
}
}
return tempEvents;
}
private RiqEntity GetLastIntervalBeforeBeat(double beat)
{
List<RiqEntity> intervalEvents = EventCaller.GetAllInGameManagerList("firstContact", new string[] { "beat intervals" });
if (intervalEvents.Count == 0) return null;
var tempEvents = intervalEvents.FindAll(x => x.beat <= beat);
tempEvents.Sort((x, y) => x.beat.CompareTo(y.beat));
return tempEvents[^1];
}
public static void PreInterval(double beat, float interval, string outDialogue, bool autoPassTurn)
{
if (GameManager.instance.currentGame == "firstContact")
{
instance.SetIntervalStart(beat, interval, outDialogue, beat, autoPassTurn);
}
else
{
queuedIntervals.Add(new QueuedInterval
{
beat = beat,
interval = interval,
outDialogue = outDialogue,
autoPassTurn = autoPassTurn
});
}
}
private void SetIntervalStart(double beat, float interval, string outDialogue, double gameSwitchBeat, bool autoPassTurn)
{
List<RiqEntity> relevantSpeakEvents = GetAllSpeaksInBetweenBeat(beat, beat + interval);
relevantSpeakEvents.Sort((x, y) => x.beat.CompareTo(y.beat));
List<BeatAction.Action> queuedSpeaks = new()
{
new BeatAction.Action(beat, delegate
{
translator.Play("translator_lookAtAlien", 0, 0);
onOutDialogue = outDialogue;
callDiagBuffer = "";
respDiagBuffer = "";
callDiagList.Clear();
callDiagIndex = 0;
alienText.text = "";
translateText.text = "";
translateFailText.text = "";
alienTextbox.SetActive(false);
translateTextbox.SetActive(false);
translateFailTextbox.SetActive(false);
if (autoPassTurn)
{
AlienTurnOver(beat + interval, beat, beat + interval, 1);
}
})
};
for (int i = 0; i < relevantSpeakEvents.Count; i++)
{
RiqEntity speakEventToCheck = relevantSpeakEvents[i];
if (speakEventToCheck.beat >= gameSwitchBeat)
{
queuedSpeaks.Add(new BeatAction.Action(speakEventToCheck.beat, delegate
{
AlienSpeak(speakEventToCheck.beat, speakEventToCheck["spaceNum"], speakEventToCheck["dotdotdot"]);
}));
}
else
{
AlienSpeakInactive(speakEventToCheck["spaceNum"]);
}
}
BeatAction.New(this, queuedSpeaks);
}
public override void OnGameSwitch(double beat)
{
if (Conductor.instance.isPlaying && !Conductor.instance.isPaused)
{
if (queuedIntervals.Count > 0)
{
foreach (var interval in queuedIntervals)
{
SetIntervalStart(interval.beat, interval.interval, interval.outDialogue, beat, interval.autoPassTurn);
}
queuedIntervals.Clear();
}
}
}
private void Update()
{
if (Conductor.instance.ReportBeat(ref lastReportedBeat, offset: liveBarBeatOffset))
{
liveBar.Play("liveBar", 0, 0);
}
else if (Conductor.instance.songPositionInBeatsAsDouble < lastReportedBeat)
{
lastReportedBeat = Math.Round(Conductor.instance.songPositionInBeatsAsDouble);
}
if (PlayerInput.GetIsAction(InputAction_Press) && !IsExpectingInputNow(InputAction_Press))
{
translator.DoScaledAnimationAsync("translator_eh", 0.5f);
if (isSpeaking)
{
if (callDiagIndex == 0)
FailContact();
else
TrailingContact();
}
else if (!noHitOnce && !missionControl.activeInHierarchy)
{
SoundByte.PlayOneShotGame("firstContact/ALIEN_PLAYER_MISS2_A", -1, SoundByte.GetPitchFromSemiTones(UnityEngine.Random.Range(-2, 1), false));
}
}
}
public void LiveBarBeat(bool onBeat)
{
if (onBeat)
{
liveBarBeatOffset = 0;
}
else
{
liveBarBeatOffset = .5f;
}
}
public void LookAtDirection(int alienLookAt, int translatorLookAt)
{
switch (alienLookAt)
{
case 0:
alien.Play("alien_lookAt", 0, 0);
break;
case 1:
alien.Play("alien_idle", 0, 0);
break;
}
switch (translatorLookAt)
{
case 0:
translator.Play("translator_lookAtAlien", 0, 0);
break;
case 1:
translator.Play("translator_idle", 0, 0);
break;
}
}
private void AlienSpeak(double beat, int spaceNum, bool hasDDD)
{
int voiceline = UnityEngine.Random.Range(1, 11);
if (voiceline == currentVoicelineIndex) voiceline++;
if (voiceline > 10) voiceline = 1;
currentVoicelineIndex = voiceline;
SoundByte.PlayOneShotGame("firstContact/Bob" + voiceline, beat, SoundByte.GetPitchFromCents(UnityEngine.Random.Range(-100, 0), false));
SoundByte.PlayOneShotGame("firstContact/BobB");
alien.DoScaledAnimationAsync("alien_talk", 0.5f);
if (UnityEngine.Random.Range(0, 5) == 0) translator.DoScaledAnimationAsync("translator_lookAtAlien_nod", 0.5f);
alienTextbox.SetActive(true);
for (int i = 0; i < spaceNum * 2; i++)
{
callDiagBuffer += " ";
}
callDiagBuffer += hasDDD ? MSG_MAN_DDD : MSG_MAN;
UpdateAlienTextbox();
}
private void AlienSpeakInactive(int spaceNum)
{
alienTextbox.SetActive(true);
for (int i = 0; i < spaceNum * 2; i++)
{
callDiagBuffer += " ";
}
callDiagBuffer += MSG_MAN;
UpdateAlienTextbox();
}
public void PassTurnStandalone(double beat)
{
RiqEntity lastInterval = GetLastIntervalBeforeBeat(beat);
float length = EventCaller.GetAllInGameManagerList("firstContact", new string[] { "alien turnover" }).Find(x => x.beat == beat).length;
if (lastInterval == null) return;
AlienTurnOver(beat, lastInterval.beat, lastInterval.beat + lastInterval.length, length);
}
private void AlienTurnOver(double beat, double intervalBeat, double endBeat, float length)
{
var inputs = GetAllSpeaksInBetweenBeat(intervalBeat, endBeat);
inputs.Sort((x, y) => x.beat.CompareTo(y.beat));
for (int i = 0; i < inputs.Count; i++)
{
var input = inputs[i];
double relativeBeat = input.beat - intervalBeat;
ScheduleInput(beat, length + relativeBeat, InputAction_Press, AlienTapping, AlienOnMiss, AlienEmpty);
callDiagList.Add((input["dialogue"], input["newline"]));
}
BeatAction.New(this, new List<BeatAction.Action>()
{
new BeatAction.Action(beat, delegate
{
isSpeaking = true;
SoundByte.PlayOneShotGame("firstContact/turnover");
alienTextbox.SetActive(false);
alien.Play("alien_point", 0, 0);
}),
new BeatAction.Action(beat + (length / 2), delegate
{
alien.Play("alien_idle", 0, 0);
})
});
}
public void AlienSuccess(double beat)
{
string animString = "";
List<MultiSound.Sound> sound = new List<MultiSound.Sound>();
if (!(hasMissed || noHitOnce))
{
sound = new List<MultiSound.Sound>()
{
new MultiSound.Sound("firstContact/successCrowd", beat),
new MultiSound.Sound("firstContact/nod", beat),
new MultiSound.Sound("firstContact/nod", beat + 0.5f),
new MultiSound.Sound("firstContact/successExtra" + UnityEngine.Random.Range(1, 3), beat + 0.5f, SoundByte.GetPitchFromCents(UnityEngine.Random.Range(-50, 50), false)),
new MultiSound.Sound("firstContact/whistle", beat + UnityEngine.Random.Range(0.5f, 1.5f), SoundByte.GetPitchFromCents(UnityEngine.Random.Range(-50, 100), false), UnityEngine.Random.Range(0.4f, 1f)),
};
animString = "alien_success";
}
else
{
sound = new List<MultiSound.Sound>()
{
new MultiSound.Sound("firstContact/fail", beat),
new MultiSound.Sound("firstContact/shakeHead", beat),
new MultiSound.Sound("firstContact/shakeHead", beat + 0.5f),
};
animString = "alien_fail";
}
MultiSound.Play(sound.ToArray());
BeatAction.New(this, new List<BeatAction.Action>()
{
new BeatAction.Action(beat, delegate { alien.Play(animString, 0, 0); }),
new BeatAction.Action(beat + .5f, delegate { alien.Play(animString, 0, 0); }),
new BeatAction.Action(beat + 1, delegate { alienTextbox.SetActive(false); translateTextbox.SetActive(false); translateFailTextbox.SetActive(false); })
});
isSpeaking = false;
hasMissed = false;
noHitOnce = false;
}
void UpdateAlienTextbox()
{
alienText.text = callDiagBuffer;
}
string GetMessageFromCallDialogue(int callDiagIndex = 0)
{
StringBuilder sb = new StringBuilder();
for (int i = callDiagIndex; i < callDiagList.Count; i++)
{
(string s, bool showNewline) = callDiagList[i];
if (showNewline && callDiagIndex != i)
{
break;
}
sb.Append(s);
}
return sb.ToString();
}
void UpdateTranslateTextbox()
{
if (callDiagIndex == 0 && !hasMissed && !noHitOnce)
{
ResetTranslateTextbox();
}
translateText.text = respDiagBuffer;
translateFailText.text = respDiagBuffer;
}
void ResetTranslateTextbox(bool destroyText = false)
{
// shift the textbox to centre the message
string message = GetMessageFromCallDialogue(callDiagIndex);
// many hardcoded values there'll be a better way to do this
Vector2 size = translateText.GetPreferredValues(message, 10.95f, 2);
translateText.rectTransform.anchoredPosition = new Vector2(Mathf.Max((10.95f / 2f) + (-size.x / 2 - 0.25f), -0.25f), Mathf.Max((2.11f / 2f) + (-size.y / 2) + 0.2f, 0.2f));
if (destroyText)
{
respDiagBuffer = "";
translateText.text = "";
}
}
public void MissionControlDisplay(double beat, bool stay, float length)
{
missionControl.SetActive(true);
alienTextbox.SetActive(false);
translateTextbox.SetActive(false);
translateFailTextbox.SetActive(false);
string textToPut = "";
if (!(hasMissed || noHitOnce))
{
textToPut = "missionControl_success";
}
else
{
textToPut = "missionControl_fail";
}
missionControl.GetComponentInParent<Animator>().Play(textToPut, 0, 0);
alien.Play("alien_idle", 0, 0);
translator.Play("translator_idle", 0, 0);
if (!stay)
{
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(beat + length, delegate { missionControl.SetActive(false); }),
});
}
else
{
missionControl.SetActive(true);
}
isSpeaking = false;
}
void FailContact()
{
SoundByte.PlayOneShotGame("firstContact/failContact");
translator.DoScaledAnimationAsync("translator_speak", 0.5f);
if (!hasMissed && callDiagIndex == 0)
{
translateFailTextbox.SetActive(true);
respDiagBuffer = onOutDialogue;
UpdateTranslateTextbox();
ScoreMiss();
}
hasMissed = true;
}
void TrailingContact()
{
SoundByte.PlayOneShotGame("firstContact/slightlyFail");
translator.Play("translator_eh", 0, 0);
if (!hasMissed)
{
respDiagBuffer += MID_MSG_MISS;
UpdateTranslateTextbox();
}
hasMissed = true;
}
public void AlienTapping(PlayerActionEvent caller, float state) //OnHit
{
if (hasMissed && callDiagIndex == 0)
{
caller.isEligible = false;
ScoreMiss();
return;
};
if (noHitOnce)
{
caller.isEligible = false;
FailContact();
return;
}
if (state >= 1f || state <= -1f)
{
SoundByte.PlayOneShotGame("firstContact/ALIEN_PLAYER_A", -1, SoundByte.GetPitchFromSemiTones(UnityEngine.Random.Range(-3, 3), false));
translator.DoScaledAnimationAsync("translator_speak", 0.5f);
if (callDiagIndex == 0) return;
TrailingContact();
return;
}
translator.DoScaledAnimationAsync("translator_speak", 0.5f);
SoundByte.PlayOneShotGame("firstContact/ALIEN_PLAYER_A", -1, SoundByte.GetPitchFromSemiTones(UnityEngine.Random.Range(-3, 3), false));
SoundByte.PlayOneShotGame("firstContact/ALIEN_PLAYER_B");
if (hasMissed)
{
caller.isEligible = false;
return;
}
else
{
(string dialogue, bool showNewline) = callDiagList[callDiagIndex];
if (showNewline)
{
ResetTranslateTextbox(true);
}
respDiagBuffer += dialogue;
translateTextbox.SetActive(true);
UpdateTranslateTextbox();
callDiagIndex++;
}
}
public void AlienOnMiss(PlayerActionEvent caller) //OnMiss
{
if (!noHitOnce)
{
SoundByte.PlayOneShotGame("firstContact/alienNoHit");
noHitOnce = true;
}
if (callDiagIndex > 0 && !hasMissed)
{
respDiagBuffer += MID_MSG_MISS;
UpdateTranslateTextbox();
hasMissed = true;
}
alien.Play("alien_noHit", 0, 0);
}
public void AlienEmpty(PlayerActionEvent caller) { } //OnEmpty
}
}