GameState.cs
namespace NiuLai;
/// <summary>Film order. The game is the movie, just playable and still crooked.</summary>
public enum Beat
{
Migrate,
Dream,
Snake,
Creek,
Night,
Suspect,
Wolves,
Ban,
Canyon,
Cart,
Farewell,
Wake,
Reunion,
Credits
}
public static class GameState
{
public static Beat Beat { get; private set; } = Beat.Migrate;
public static int GrassEaten { get; private set; }
public static int GrassBag { get; private set; }
public static int Courage { get; private set; }
public static bool Bitten { get; private set; }
public static bool CreekJumped { get; private set; }
public static bool FedLeopard { get; private set; }
public static bool HerdSuspects { get; private set; }
public static bool BaoLaBanned { get; private set; }
public static bool MomGone { get; private set; }
public static bool MooedCart { get; private set; }
public static bool FoundDad { get; private set; }
public static bool HeardLikeHere { get; private set; }
public static bool HasMilk { get; private set; }
public static bool GaveMilk { get; private set; }
public static bool SawLeopardFace { get; private set; }
public static bool DidHeadbutt { get; private set; }
public static int CartMoos { get; private set; }
public static bool DidOpenCut { get; private set; }
public static bool DidEatNag { get; private set; }
public static int MooCount { get; private set; }
public static int ExtraShares { get; private set; }
public static bool Won { get; set; }
public static bool SongWalk { get; set; }
public static bool SawTail { get; private set; }
public static bool WantHop { get; set; }
public static bool InVoid => Beat is Beat.Dream or Beat.Farewell;
public static int SceneScore
{
get
{
var n = 0;
if ( DidOpenCut ) n++;
if ( HeardLikeHere ) n++;
if ( Bitten ) n++;
if ( CreekJumped ) n++;
if ( SawTail ) n++;
if ( SawLeopardFace ) n++;
if ( GaveMilk ) n++;
if ( FedLeopard ) n++;
if ( DidHeadbutt ) n++;
if ( HerdSuspects ) n++;
if ( BaoLaBanned ) n++;
if ( MomGone ) n++;
if ( MooedCart ) n++;
if ( FoundDad ) n++;
return n;
}
}
public const int SceneTotal = 14;
public static bool IsNight => Beat is Beat.Night or Beat.Suspect or Beat.Wolves or Beat.Ban;
public static bool WolvesHunt => Beat is Beat.Wolves or Beat.Canyon;
public static bool BaoLaFollows => FedLeopard && !BaoLaBanned && Beat < Beat.Farewell;
public static float Growth => 0.62f + GrassEaten * 0.05f + Courage * 0.01f + ExtraShares * 0.03f + (FoundDad ? 0.2f : 0);
public static float WalkMul => 0.80f + GrassEaten * 0.018f + Courage * 0.008f;
public static float PlayTime { get; private set; }
public static readonly Vector3 CampPos = new( 0, 0, 24 );
public static readonly Vector3 DreamPos = new( 0, 2800, 16 );
public static readonly Vector3 SnakePos = new( 280, 20, 24 );
public static readonly Vector3 WakePos = new( 40, 40, 24 );
public static readonly Vector3 DadPos = new( 160, 860, 24 );
public static Line[] Dialogue { get; private set; }
public static int DialogueIndex { get; private set; }
public static string Speaker { get; private set; }
public static bool Talking => Dialogue is { Length: > 0 };
public static string PromptCn { get; private set; }
public static string PromptEn { get; private set; }
public static Action Teleport;
public static Action MoodChanged;
public static string ChapterCn => Beat switch
{
Beat.Migrate => "迁徙",
Beat.Dream => "纯黑的梦",
Beat.Snake => "小绳头",
Beat.Creek => "小溪",
Beat.Night => "豹拉",
Beat.Suspect => "怀疑",
Beat.Wolves => "狼来了",
Beat.Ban => "不许跟着",
Beat.Canyon => "峡谷",
Beat.Cart => "台车",
Beat.Farewell => "道别",
Beat.Wake => "醒来",
Beat.Reunion => "团聚",
_ => "雨后轻风有香"
};
public static string ChapterEn => Beat switch
{
Beat.Migrate => "The walk",
Beat.Dream => "Black dream",
Beat.Snake => "Little Rope-Head",
Beat.Creek => "The creek",
Beat.Night => "Bao La",
Beat.Suspect => "They doubt him",
Beat.Wolves => "Wolves come",
Beat.Ban => "Don't follow",
Beat.Canyon => "Canyon",
Beat.Cart => "The cart",
Beat.Farewell => "Goodbye",
Beat.Wake => "Wake",
Beat.Reunion => "Together",
_ => "After rain"
};
public static Line QuestLine => Lines.Quest( Beat );
public static Line CurrentLine =>
Talking && DialogueIndex >= 0 && DialogueIndex < Dialogue.Length
? Dialogue[DialogueIndex]
: default;
public static void Reset()
{
Beat = Beat.Migrate;
GrassEaten = 0;
GrassBag = 0;
Courage = 0;
Bitten = false;
CreekJumped = false;
FedLeopard = false;
HerdSuspects = false;
BaoLaBanned = false;
MomGone = false;
MooedCart = false;
FoundDad = false;
HeardLikeHere = false;
HasMilk = false;
GaveMilk = false;
SawLeopardFace = false;
DidHeadbutt = false;
CartMoos = 0;
DidOpenCut = false;
DidEatNag = false;
SawTail = false;
WantHop = false;
MooCount = 0;
ExtraShares = 0;
Won = false;
SongWalk = false;
PlayTime = 0;
ClearTalk();
SetPrompt( "", "" );
}
public static void SetPrompt( string cn, string en )
{
cn ??= "";
en ??= "";
if ( PromptCn == cn && PromptEn == en )
return;
PromptCn = cn;
PromptEn = en;
}
public static void Tick( float dt )
{
if ( GameFlow.Screen != GameScreen.Playing )
return;
PlayTime += dt;
_prove += dt;
if ( _prove > 44f && !Talking && !FilmCut.Playing )
{
_prove = 0;
Chatter.Say( "旁白 / Narrator", Chatter.Pick( Lines.TimeProve ), 2.4f );
}
if ( Beat == Beat.Reunion && !Won && !Talking )
GameFlow.Win();
}
static float _prove;
public static void Talk( string speaker, Line[] lines )
{
if ( lines is not { Length: > 0 } )
return;
Speaker = speaker;
Dialogue = lines;
DialogueIndex = 0;
}
public static bool AdvanceTalk()
{
if ( !Talking )
return false;
DialogueIndex++;
if ( DialogueIndex < Dialogue.Length )
return false;
ClearTalk();
OnTalkEnd();
return true;
}
public static void ClearTalk()
{
Dialogue = null;
DialogueIndex = 0;
Speaker = "";
}
static void OnTalkEnd()
{
if ( Beat == Beat.Migrate && !DidOpenCut )
{
DidOpenCut = true;
FilmCut.Play( CutId.OpenEyes );
}
else if ( Beat == Beat.Dream )
LeaveDream();
else if ( Beat == Beat.Cart && MooedCart )
EnterFarewell();
else if ( Beat == Beat.Farewell )
WakeUp();
else if ( Beat == Beat.Reunion && !Won )
GameFlow.Win();
}
public static void HearLikeHere()
{
if ( HeardLikeHere )
return;
HeardLikeHere = true;
Talk( "云玎 / Yun Ding", Lines.LikeHere );
GameAudio.PlayClip( GameAudio.LikeHere );
}
public static void EnterDreamFromCut()
{
if ( Beat != Beat.Migrate )
return;
Advance( Beat.Dream );
Courage += 4;
Trophies.Unlock( "yun_ding" );
Teleport?.Invoke();
MoodChanged?.Invoke();
}
public static void FinishFarewellCut()
{
if ( Beat == Beat.Farewell )
WakeUp();
}
public static void GiveMilk()
{
if ( HasMilk )
{
Talk( "牛妈妈 / Mama", new[] { new Line( "杯子已经给你了。", "Cup already give you." ) } );
return;
}
HasMilk = true;
Talk( "牛妈妈 / Mama", Lines.MomMilk );
GameAudio.PlayClip( GameAudio.MamaOk );
}
public static void PourMilk()
{
if ( GaveMilk )
return;
if ( !HasMilk )
{
Talk( "豹拉 / Bao La", new[] { new Line( "先去找妈妈要奶。", "First go find Mama ask milk." ) } );
return;
}
GaveMilk = true;
HasMilk = false;
Courage += 6;
FilmCut.Play( CutId.MilkDad );
}
public static bool TryLeopardMeet()
{
if ( !SawLeopardFace )
{
SawLeopardFace = true;
FilmCut.Play( CutId.LeopardFace );
return true;
}
if ( !GaveMilk )
{
PourMilk();
return true;
}
if ( !FedLeopard )
return FeedLeopard();
return false;
}
static float _eatComboUntil;
static int _eatCombo;
public static void MarkEatNag() => DidEatNag = true;
public static void MarkTail() => SawTail = true;
public static void CourageBoost( int n )
{
Courage = Math.Min( 100, Courage + Math.Max( 0, n ) );
}
public static void EatGrass()
{
GrassEaten++;
GrassBag++;
Courage = Math.Min( 100, Courage + 2 );
if ( PlayTime <= _eatComboUntil )
_eatCombo++;
else
_eatCombo = 1;
_eatComboUntil = PlayTime + 8f;
if ( _eatCombo >= 3 )
{
_eatCombo = 0;
Courage = Math.Min( 100, Courage + 3 );
Chatter.Say( "牛来 / Niu Lai", new Line( "连吃三丛。我明显大了。", "Eat three tuft. I obvious bigger." ), 3.2f );
GameAudio.PlayCrowd( GameAudio.CrowdMilk );
}
else
{
Chatter.Say( "草 / Grass", Chatter.Pick( Lines.Grass ) );
}
}
public static bool ShareAgain()
{
if ( !FedLeopard || GrassBag <= 0 || BaoLaBanned )
return false;
GrassBag--;
ExtraShares++;
Courage = Math.Min( 100, Courage + 1 );
Chatter.Say( "豹拉 / Bao La", Chatter.Pick( Lines.ExtraMilk ), 3.1f );
GameAudio.PlayClip( GameAudio.Milk, 0.8f );
return true;
}
public static void MooWorld( string nearId )
{
MooCount++;
if ( Beat == Beat.Cart )
{
MooCart();
return;
}
GameAudio.PlayClip( GameAudio.Moo );
if ( MooCount == 8 || MooCount == 20 )
GameAudio.PlayCrowd( GameAudio.CrowdCart );
var line = nearId switch
{
"mom" => new Line( "你慢点。别对着妈妈哞。", "You slow a bit. Don't moo at Mama." ),
"snake" => new Line( "小绳头抖了一下。还是绳子。", "Little Rope-Head shake once. Still rope." ),
"leopard" => new Line( "急什么。我也想哞。", "Rush what. I also want moo." ),
"rock" => new Line( "狼没理。石头也没理。", "Wolf not care. Rock also not care." ),
"lark" => new Line( "嗯。", "Ng." ),
"cart" => new Line( "它听见表扬。又大了一点。", "It hear praise. Bigger a little." ),
"niuer" => new Line( "身份不对。哞也不对。", "Identity not right. Moo also not right." ),
_ => Chatter.Pick( Lines.MooLines )
};
Chatter.Say( "哞 / Moo", line );
}
public static void MeetLark()
{
if ( Beat != Beat.Migrate )
return;
Advance( Beat.Dream );
Courage += 4;
Trophies.Unlock( "yun_ding" );
Talk( "云玎 / Yun Ding", Lines.Dream );
GameAudio.PlayClip( GameAudio.LarkNg );
Teleport?.Invoke();
MoodChanged?.Invoke();
}
public static void LeaveDream()
{
if ( Beat != Beat.Dream )
return;
Advance( Beat.Snake );
Teleport?.Invoke();
MoodChanged?.Invoke();
}
public static void GetBitten()
{
if ( Bitten )
return;
Bitten = true;
Courage += 6;
Talk( "小绳头 / Little Rope-Head", Lines.SnakeBite );
if ( Beat == Beat.Snake )
Advance( Beat.Creek );
}
public static void JumpCreek()
{
if ( CreekJumped )
return;
CreekJumped = true;
Courage += 10;
WantHop = true;
Talk( "旁白 / Narrator", Lines.Creek );
if ( Beat == Beat.Creek )
{
Advance( Beat.Night );
MoodChanged?.Invoke();
}
}
public static bool FeedLeopard()
{
if ( FedLeopard )
return false;
if ( GrassBag <= 0 )
{
Trophies.Unlock( "empty_bag" );
return false;
}
GrassBag--;
FedLeopard = true;
Courage += 8;
Talk( "豹拉 / Bao La", Lines.Leopard );
if ( !DidHeadbutt )
{
DidHeadbutt = true;
FilmCut.Play( CutId.Headbutt );
}
if ( Beat == Beat.Night && GaveMilk )
Advance( Beat.Suspect );
return true;
}
public static void HearSuspect()
{
if ( HerdSuspects )
return;
HerdSuspects = true;
Courage += 3;
Talk( "牛二 / Niu Er", Lines.Suspect );
FilmCut.Play( CutId.TooClose );
if ( Beat == Beat.Suspect )
Advance( Beat.Wolves );
MoodChanged?.Invoke();
}
public static void FirstWolfWave()
{
if ( Beat != Beat.Wolves )
return;
Talk( "旁白 / Narrator", Lines.WolvesSave );
Advance( Beat.Ban );
}
public static void BanBaoLa()
{
if ( BaoLaBanned )
return;
BaoLaBanned = true;
Talk( "牛群 / The herd", Lines.Ban );
if ( Beat == Beat.Ban )
Advance( Beat.Canyon );
}
public static void HearMom()
{
if ( MomGone )
return;
MomGone = true;
Courage += 12;
Talk( "牛妈妈 / Mama", Lines.MomFarewell );
GameAudio.PlayClip( GameAudio.CanRest );
if ( Beat == Beat.Canyon )
Advance( Beat.Cart );
}
public static void MooCart()
{
CartMoos++;
GameAudio.PlayClip( GameAudio.Moo );
GameAudio.PlayCrowd( GameAudio.CrowdCart );
if ( CartMoos == 1 )
FilmCut.Play( CutId.NoRoad );
if ( CartMoos < 3 )
{
Talk( "台车 / The Cart", new[]
{
new Line( $"哞 ×{CartMoos}。它更大了。", $"MOO ×{CartMoos}. It more big." )
} );
return;
}
if ( MooedCart )
return;
MooedCart = true;
Courage += 14;
Talk( "云玎 / Yun Ding", Lines.Cart );
}
static void EnterFarewell()
{
Advance( Beat.Farewell );
Teleport?.Invoke();
MoodChanged?.Invoke();
FilmCut.Play( CutId.Ng );
}
public static void WakeUp()
{
if ( Beat != Beat.Farewell )
return;
Advance( Beat.Wake );
Courage += 6;
Talk( "旁白 / Narrator", Lines.Wake );
Teleport?.Invoke();
MoodChanged?.Invoke();
}
public static void MeetDad()
{
if ( FoundDad )
return;
FoundDad = true;
Courage += 10;
Talk( "牛爸爸 / Cow Father", Lines.Dad );
Advance( Beat.Reunion );
SongWalk = true;
GameAudio.PlaySong();
}
public static void Advance( Beat next )
{
if ( next > Beat )
Beat = next;
}
}