mirror of
https://gitgud.io/c0ffeeeeeeee/rimworld-animations.git
synced 2026-06-18 19:35:58 +00:00
468 lines
16 KiB
C#
468 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RimWorld;
|
|
using rjw;
|
|
using UnityEngine;
|
|
using Verse;
|
|
using Verse.AI;
|
|
using Verse.Sound;
|
|
|
|
namespace Rimworld_Animations {
|
|
public class CompExtendedAnimator : ThingComp
|
|
{
|
|
|
|
// CompExtendedAnimator
|
|
// Helps manage AnimationQueue, AbsolutePosition
|
|
//ticks of current animation
|
|
private int animationTicks, actorIndex;
|
|
|
|
private int ticksSinceLastVoicePlayed, ticksUntilNextVoice;
|
|
|
|
private GroupAnimationDef currentGroupAnimation;
|
|
|
|
private List<AnimationDef> animationQueue;
|
|
private BaseExtendedAnimatorAnchor anchor;
|
|
private VoiceDef voice;
|
|
private VoiceTagDef lastVoiceTag;
|
|
|
|
|
|
private bool isAnimating = false;
|
|
|
|
public bool IsAnimating
|
|
{
|
|
get
|
|
{
|
|
return isAnimating;
|
|
}
|
|
}
|
|
|
|
public bool IsAnchored
|
|
{
|
|
get
|
|
{
|
|
return anchor != null;
|
|
}
|
|
}
|
|
|
|
private Vector3 offset;
|
|
|
|
public Vector3? Offset
|
|
{
|
|
get
|
|
{
|
|
return offset;
|
|
}
|
|
set
|
|
{
|
|
this.offset = value ?? Vector3.zero;
|
|
}
|
|
}
|
|
|
|
private int rotation;
|
|
|
|
public int? Rotation
|
|
{
|
|
get
|
|
{
|
|
return rotation;
|
|
}
|
|
set
|
|
{
|
|
this.rotation = value ?? 0;
|
|
}
|
|
}
|
|
|
|
public int AnimationLength
|
|
{
|
|
get
|
|
{
|
|
if (!IsAnimating) return 0;
|
|
|
|
int groupAnimLength = 0;
|
|
foreach(AnimationDef anim in animationQueue)
|
|
{
|
|
groupAnimLength += anim.durationTicks;
|
|
}
|
|
|
|
return groupAnimLength;
|
|
|
|
}
|
|
}
|
|
|
|
public AnimationDef CurrentAnimation {
|
|
|
|
get
|
|
{
|
|
return IsAnimating ? animationQueue[0] : null;
|
|
}
|
|
|
|
}
|
|
|
|
public GroupAnimationDef CurrentGroupAnimation
|
|
{
|
|
get
|
|
{
|
|
return currentGroupAnimation;
|
|
}
|
|
set
|
|
{
|
|
currentGroupAnimation = value;
|
|
}
|
|
}
|
|
|
|
public int ActorIndex
|
|
{
|
|
get
|
|
{
|
|
return actorIndex;
|
|
}
|
|
}
|
|
|
|
public override void PostSpawnSetup(bool respawningAfterLoad)
|
|
{
|
|
if (voice == null)
|
|
{
|
|
AssignNewVoice();
|
|
}
|
|
|
|
}
|
|
|
|
public Vector3 getAnchor()
|
|
{
|
|
return anchor.getDrawPos();
|
|
}
|
|
|
|
public override void CompTick()
|
|
{
|
|
if (isAnimating)
|
|
{
|
|
animationTicks++;
|
|
|
|
//if animationticks is equal to cur. anim duration,
|
|
if (animationTicks >= animationQueue[0].durationTicks)
|
|
{
|
|
//dequeue; returns false if more animations
|
|
if (!PopAnimationQueue())
|
|
{
|
|
//play next if more anims still
|
|
PlayNextAnimation();
|
|
}
|
|
else
|
|
{
|
|
StopAnimating();
|
|
}
|
|
}
|
|
|
|
CheckAndPlaySounds();
|
|
|
|
}
|
|
|
|
|
|
|
|
base.CompTick();
|
|
}
|
|
|
|
//returns false if still more animations
|
|
public bool PopAnimationQueue()
|
|
{
|
|
|
|
if (!animationQueue.Empty())
|
|
{
|
|
//pop queue
|
|
animationQueue.RemoveAt(0);
|
|
}
|
|
|
|
return animationQueue.Empty();
|
|
}
|
|
|
|
public void PlayNextAnimation()
|
|
{
|
|
if (!animationQueue.Empty())
|
|
{
|
|
isAnimating = true;
|
|
animationTicks = 0;
|
|
pawn.Drawer.renderer.SetAnimation(animationQueue[0]);
|
|
}
|
|
}
|
|
|
|
public void StopAnimating()
|
|
{
|
|
isAnimating = false;
|
|
animationQueue = null;
|
|
anchor = null;
|
|
offset = Vector3.zero;
|
|
pawn.Drawer.renderer.SetAnimation(null);
|
|
CurrentGroupAnimation = null;
|
|
actorIndex = 0;
|
|
pawn.Drawer.renderer.SetAllGraphicsDirty();
|
|
}
|
|
|
|
public void PlayGroupAnimation(GroupAnimationDef groupAnimationDef, int actorIndex, int randomSeed, List<Pawn> actors)
|
|
{
|
|
|
|
this.actorIndex = actorIndex;
|
|
this.currentGroupAnimation = groupAnimationDef;
|
|
ticksSinceLastVoicePlayed = GenTicks.TicksGame;
|
|
ticksUntilNextVoice = 0;
|
|
|
|
groupAnimationDef.GetOffset(actorIndex, this.pawn, out Vector3? offset, out int? rotation);
|
|
this.Offset = offset;
|
|
this.Rotation = rotation;
|
|
|
|
|
|
animationQueue = groupAnimationDef.GetAllAnimationsForActor(actorIndex, randomSeed, actors);
|
|
|
|
|
|
|
|
//set all graphics dirty; necessary because sometimes rjw doesn't call during threesomes
|
|
pawn.Drawer.renderer.SetAllGraphicsDirty();
|
|
|
|
PlayNextAnimation();
|
|
}
|
|
|
|
public void PlayGroupAnimation(GroupAnimationDef groupAnimationDef, BaseExtendedAnimatorAnchor anchor, int animationIndex, int randomSeed, List<Pawn> actors)
|
|
{
|
|
this.anchor = anchor;
|
|
PlayGroupAnimation(groupAnimationDef, animationIndex, randomSeed, actors);
|
|
}
|
|
|
|
public override void PostExposeData()
|
|
{
|
|
base.PostExposeData();
|
|
Scribe_Values.Look<bool>(ref this.isAnimating, "animations_isAnimating", false);
|
|
Scribe_Values.Look<int>(ref this.animationTicks, "animations_ticks", 0);
|
|
Scribe_Values.Look<int>(ref this.actorIndex, "actor_index", 0);
|
|
Scribe_Collections.Look<AnimationDef>(ref animationQueue, "animations_queue");
|
|
Scribe_Defs.Look<GroupAnimationDef>(ref currentGroupAnimation, "animations_groupAnimationDef");
|
|
Scribe_Deep.Look<BaseExtendedAnimatorAnchor>(ref this.anchor, "animations_anchor");
|
|
Scribe_Values.Look<Vector3>(ref this.offset, "animations_offset", Vector3.zero);
|
|
Scribe_Values.Look<int>(ref this.rotation, "animations_rotationOffset", 0);
|
|
Scribe_Defs.Look<VoiceDef>(ref this.voice, "animations_voice");
|
|
|
|
}
|
|
|
|
public override List<PawnRenderNode> CompRenderNodes()
|
|
{
|
|
//only if pawn is animating for performance
|
|
if (IsAnimating)
|
|
{
|
|
|
|
List<PawnRenderNode> animRenderNodes = new List<PawnRenderNode>();
|
|
|
|
// for all animationpropdefs,
|
|
foreach (AnimationPropDef animationProp in DefDatabase<AnimationPropDef>.AllDefsListForReading)
|
|
{
|
|
//if animation makes use of prop,
|
|
if (AnimationMakesUseOfProp(animationProp, out PawnRenderNodeProperties props))
|
|
{
|
|
|
|
if (props.texPath.NullOrEmpty())
|
|
{
|
|
props.texPath = "AnimationProps/MissingTexture/MissingTexture";
|
|
}
|
|
|
|
|
|
//create new render node
|
|
PawnRenderNode animRenderNode = (PawnRenderNode)Activator.CreateInstance(props.nodeClass, new object[] {
|
|
this.pawn,
|
|
props,
|
|
pawn.Drawer.renderer.renderTree
|
|
});
|
|
|
|
animRenderNodes.Add(animRenderNode);
|
|
}
|
|
|
|
}
|
|
|
|
//return list of rendernodes that should animate
|
|
return animRenderNodes;
|
|
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
public void AssignNewVoice()
|
|
{
|
|
//all voice options
|
|
List<VoiceDef> voiceOptions =
|
|
DefDatabase<VoiceDef>.AllDefsListForReading
|
|
.FindAll(voiceDef => voiceDef.VoiceFitsPawn(pawn));
|
|
|
|
//all voice options, with priority (for traitdef specific voices)
|
|
List<VoiceDef> voiceOptionsWithPriority =
|
|
voiceOptions.FindAll(voiceDef => voiceDef.takesPriority);
|
|
|
|
if (!voiceOptionsWithPriority.NullOrEmpty())
|
|
{
|
|
voice = voiceOptionsWithPriority.RandomElementByWeight(x => x.randomChanceFactor);
|
|
|
|
}
|
|
else if (!voiceOptions.NullOrEmpty())
|
|
{
|
|
voice = voiceOptions.RandomElementByWeight(x => x.randomChanceFactor);
|
|
}
|
|
|
|
}
|
|
|
|
public void CheckAndPlaySounds()
|
|
{
|
|
|
|
PawnRenderNode rootNode = pawn.Drawer?.renderer?.renderTree?.rootNode;
|
|
|
|
//check if the rootnode has sounds; if so play it
|
|
if (rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)
|
|
{
|
|
SoundDef sound = animWorker.soundAtTick(rootNode.tree.AnimationTick, rootNode.tree.currentAnimation, rootNode);
|
|
|
|
if (sound != null)
|
|
{
|
|
SoundInfo soundInfo = new TargetInfo(pawn.Position, pawn.Map);
|
|
|
|
//temp; does not consider non-rjw animations
|
|
//todo: replace with value stored in comp or somewhere else?
|
|
soundInfo.volumeFactor = RJWAnimationSettings.soundVolume;
|
|
|
|
sound.PlayOneShot(soundInfo);
|
|
}
|
|
|
|
if (RJWAnimationSettings.playVoices)
|
|
{
|
|
SoundInfo voiceInfo = new TargetInfo(pawn.Position, pawn.Map);
|
|
voiceInfo.volumeFactor = RJWAnimationSettings.voicesVolume;
|
|
|
|
//play voice sounds
|
|
VoiceTagDef voiceTag = animWorker.voiceAtTick(rootNode.tree.AnimationTick, rootNode.tree.currentAnimation, rootNode);
|
|
if (voiceTag != null
|
|
&& (lastVoiceTag != voiceTag || ticksSinceLastVoicePlayed + ticksUntilNextVoice < GenTicks.TicksGame)) //play a new voice tag, or wait until time elapsed
|
|
{
|
|
lastVoiceTag = voiceTag;
|
|
ticksSinceLastVoicePlayed = GenTicks.TicksGame;
|
|
|
|
if (voice != null && voice.sounds.ContainsKey(voiceTag))
|
|
{
|
|
ticksUntilNextVoice = voice.ticksBetweenPlays.RandomInRange;
|
|
voice.sounds[voiceTag].PlayOneShot(voiceInfo);
|
|
}
|
|
|
|
else if (pawn.RaceProps.Humanlike && RJWAnimationSettings.playHumanlikeVoicesAsDefault)
|
|
{
|
|
//play default voice
|
|
VoiceDef pawnDefaultVoice = (pawn.gender == Gender.Male ? VoiceDefOf.Voice_HumanMale : VoiceDefOf.Voice_HumanFemale);
|
|
|
|
ticksUntilNextVoice = pawnDefaultVoice.ticksBetweenPlays.RandomInRange;
|
|
|
|
if (pawnDefaultVoice.sounds.ContainsKey(voiceTag))
|
|
{
|
|
ticksUntilNextVoice = pawnDefaultVoice.ticksBetweenPlays.RandomInRange;
|
|
pawnDefaultVoice.sounds[voiceTag].PlayOneShot(voiceInfo);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//check rootnodes and children
|
|
if (rootNode?.children != null)
|
|
{
|
|
foreach (PawnRenderNode node in rootNode?.children)
|
|
{
|
|
if (node?.AnimationWorker is AnimationWorker_KeyframesExtended childrenAnimWorker)
|
|
{
|
|
|
|
|
|
if (RJWAnimationSettings.playSounds)
|
|
{
|
|
SoundDef sound = childrenAnimWorker.soundAtTick(node.tree.AnimationTick, node.tree.currentAnimation, node);
|
|
|
|
if (sound != null)
|
|
{
|
|
SoundInfo soundInfo = new TargetInfo(pawn.Position, pawn.Map);
|
|
soundInfo.volumeFactor = RJWAnimationSettings.soundVolume;
|
|
sound.PlayOneShot(soundInfo);
|
|
}
|
|
}
|
|
|
|
if (RJWAnimationSettings.playVoices)
|
|
{
|
|
|
|
SoundInfo voiceInfo = new TargetInfo(pawn.Position, pawn.Map);
|
|
voiceInfo.volumeFactor = RJWAnimationSettings.voicesVolume;
|
|
|
|
//play voice sounds
|
|
VoiceTagDef voiceTag = childrenAnimWorker.voiceAtTick(rootNode.tree.AnimationTick, rootNode.tree.currentAnimation, rootNode);
|
|
if (voiceTag != null
|
|
&& (lastVoiceTag != voiceTag || ticksSinceLastVoicePlayed + ticksUntilNextVoice < GenTicks.TicksGame)) //play a new voice tag, or wait until time elapsed
|
|
{
|
|
lastVoiceTag = voiceTag;
|
|
ticksSinceLastVoicePlayed = GenTicks.TicksGame;
|
|
|
|
if (voice != null && voice.sounds.ContainsKey(voiceTag))
|
|
{
|
|
ticksUntilNextVoice = voice.ticksBetweenPlays.RandomInRange;
|
|
voice.sounds[voiceTag].PlayOneShot(voiceInfo);
|
|
}
|
|
else if (pawn.RaceProps.Humanlike && RJWAnimationSettings.playHumanlikeVoicesAsDefault)
|
|
{
|
|
|
|
VoiceDef pawnDefaultVoice = (pawn.gender == Gender.Male ? VoiceDefOf.Voice_HumanMale : VoiceDefOf.Voice_HumanFemale);
|
|
if (pawnDefaultVoice.sounds.ContainsKey(voiceTag))
|
|
{
|
|
ticksUntilNextVoice = pawnDefaultVoice.ticksBetweenPlays.RandomInRange;
|
|
pawnDefaultVoice.sounds[voiceTag].PlayOneShot(voiceInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//do the same for all the child nodes
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool AnimationMakesUseOfProp(AnimationPropDef animationProp, out PawnRenderNodeProperties props)
|
|
{
|
|
props = null;
|
|
// never true if not animating; anim props shouldn't be attached
|
|
if (!IsAnimating) return false;
|
|
|
|
//for all anims in queue (because it's only recached at start)
|
|
foreach (AnimationDef animation in animationQueue)
|
|
{
|
|
foreach (PawnRenderNodeTagDef propTag in animation.keyframeParts.Keys)
|
|
{
|
|
//get specific props for that pawn
|
|
props = animationProp.GetPawnRenderNodeProperties(this.pawn);
|
|
|
|
// if that proptag is the same as the one for animationProp,
|
|
if (propTag == props.tagDef)
|
|
{
|
|
//that prop is being used in the animation
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
private Pawn pawn => base.parent as Pawn;
|
|
|
|
}
|
|
|
|
}
|