178 lines
5.7 KiB
C#
178 lines
5.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Single deterministic action-selection pipeline.</summary>
|
|
public static class ActivityActionComposer
|
|
{
|
|
private static readonly Dictionary<string, string[]> BeatTemplates =
|
|
new Dictionary<string, string[]>(StringComparer.Ordinal)
|
|
{
|
|
["BehUnloadResources"] = new[]
|
|
{
|
|
"unloads {carrying} at {place}",
|
|
"delivers {carrying} into store{place_at}"
|
|
},
|
|
["BehThrowResources"] = new[]
|
|
{
|
|
"throws {carrying} toward storage",
|
|
"tosses {carrying} from the pack"
|
|
},
|
|
["BehSocializeTalk"] = new[]
|
|
{
|
|
"talks{with}{place_at}",
|
|
"shares words{with}{place_at}"
|
|
},
|
|
["BehTryToSocialize"] = new[]
|
|
{
|
|
"tries to socialize{with}{place_at}",
|
|
"looks for a conversation{with}{place_at}"
|
|
},
|
|
["BehPlantCrops"] = new[]
|
|
{
|
|
"plants crops{place_at}{job_as}",
|
|
"sets seed in the soil{place_at}"
|
|
},
|
|
["BehCityActorFertilizeCrop"] = new[]
|
|
{
|
|
"fertilizes the crops{place_at}{job_as}",
|
|
"feeds the soil{place_at}"
|
|
},
|
|
["BehBuildTarget"] = new[]
|
|
{
|
|
"works the build{place_at}{job_as}",
|
|
"raises construction{place_at}"
|
|
},
|
|
["BehPoopOutside"] = new[]
|
|
{
|
|
"steps aside for a private moment outdoors",
|
|
"tends to nature's call outside"
|
|
},
|
|
["BehPoopInside"] = new[]
|
|
{
|
|
"steps aside for a private moment indoors",
|
|
"tends to nature's call inside"
|
|
},
|
|
["BehBoatFishing"] = new[]
|
|
{
|
|
"fishes from the boat{place_at}",
|
|
"works the waters from aboard{place_at}"
|
|
},
|
|
["BehBoatCollectFish"] = new[]
|
|
{
|
|
"collects the catch aboard{place_at}",
|
|
"hauls in the boat's catch{place_at}"
|
|
},
|
|
["BehClaimZoneForCityActorBorder"] = new[]
|
|
{
|
|
"claims border land{place_at}",
|
|
"stakes new ground for the settlement{place_at}"
|
|
}
|
|
};
|
|
|
|
public static string Compose(
|
|
ActivityContext ctx,
|
|
string key,
|
|
string rawFact,
|
|
bool hasTarget,
|
|
long subjectId,
|
|
int lineIndex)
|
|
{
|
|
ctx ??= ActivityContext.Empty;
|
|
ProseVoice voice = EnsureVoice(ctx);
|
|
|
|
if (!string.IsNullOrEmpty(key)
|
|
&& BeatTemplates.TryGetValue(key, out string[] beatBag)
|
|
&& voice.AssetKind != ActivityAssetKind.Vehicle)
|
|
{
|
|
return Pick(beatBag, voice, key, subjectId, lineIndex);
|
|
}
|
|
|
|
string verb = ActivityVerbMap.Resolve(key);
|
|
if (string.IsNullOrEmpty(verb))
|
|
{
|
|
string factual = ActivityProse.FactualActionPhrase(rawFact, key, hasTarget);
|
|
factual = ActivityClauseAssembler.StripSpeciesTokensPublic(factual, ctx);
|
|
if (!string.IsNullOrEmpty(factual))
|
|
{
|
|
return factual;
|
|
}
|
|
|
|
string patterned = ActivityProse.PatternActionPhrase(key, hasTarget);
|
|
if (!string.IsNullOrEmpty(patterned))
|
|
{
|
|
return patterned;
|
|
}
|
|
|
|
string humanized = ActivityProse.HumanizeKeyPublic(key);
|
|
verb = string.IsNullOrEmpty(humanized) ? "move" : humanized.ToLowerInvariant();
|
|
}
|
|
|
|
string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget);
|
|
return Pick(bag, voice, verb, subjectId, lineIndex);
|
|
}
|
|
|
|
private static ProseVoice EnsureVoice(ActivityContext ctx)
|
|
{
|
|
if (ctx.Voice != null
|
|
&& ctx.Voice.LiveSpeciesId.Equals(ctx.SpeciesId ?? "", StringComparison.Ordinal))
|
|
{
|
|
return ctx.Voice;
|
|
}
|
|
|
|
ProseVoice voice = ActivityVoiceResolver.Resolve(ctx.SpeciesId);
|
|
ctx.Voice = voice;
|
|
ctx.BaseSpeciesId = voice.BaseSpeciesId;
|
|
ctx.ModifierTag = ActivityVoiceResolver.ModifierName(voice.Modifier);
|
|
ctx.Family = voice.BaseFamily;
|
|
ctx.MannerTag = ActivityVoiceResolver.TagName(voice.EffectiveActionTag);
|
|
ctx.IsCiv = voice.BaseIsCiv;
|
|
ctx.IsHumanoid = voice.BaseIsHumanoid;
|
|
ctx.IsAnimal = voice.BaseIsAnimal;
|
|
return voice;
|
|
}
|
|
|
|
private static string Pick(
|
|
string[] bag,
|
|
ProseVoice voice,
|
|
string verb,
|
|
long subjectId,
|
|
int lineIndex)
|
|
{
|
|
if (bag == null || bag.Length == 0)
|
|
{
|
|
return "moves through the living moment{place_at}{job_as}";
|
|
}
|
|
|
|
if (bag.Length == 1)
|
|
{
|
|
return bag[0];
|
|
}
|
|
|
|
int slot = VerbSlot(verb);
|
|
int bit = voice.AssetKind == ActivityAssetKind.Vehicle
|
|
? 0
|
|
: ((voice.FlavorOrdinal >> slot) & 1);
|
|
int actorVariation = (int)((subjectId ^ (lineIndex * 397L)) & 1L);
|
|
int index = (bit ^ actorVariation) % Math.Min(2, bag.Length);
|
|
return bag[index];
|
|
}
|
|
|
|
private static int VerbSlot(string verb)
|
|
{
|
|
switch ((verb ?? "").ToLowerInvariant())
|
|
{
|
|
case "move": return 0;
|
|
case "wait": return 1;
|
|
case "play": return 2;
|
|
case "eat": return 3;
|
|
case "sleep": return 4;
|
|
case "hunt": return 5;
|
|
case "fight": return 6;
|
|
case "social": return 7;
|
|
case "laugh": return 8;
|
|
default: return 9;
|
|
}
|
|
}
|
|
}
|