added extended animator, animationstages, animator selects animations to play iteratively and queues them to be played; saving and loading as well

This commit is contained in:
c0ffee 2024-04-18 15:32:31 -07:00
parent db4c2d3833
commit 43843a398f
26 changed files with 389 additions and 85 deletions

Binary file not shown.

View file

@ -2,7 +2,7 @@
<Defs>
<AnimationDef>
<defName>TestAnimation2</defName>
<durationTicks>200</durationTicks>
<durationTicks>50</durationTicks>
<startOnRandomTick>False</startOnRandomTick>
<playWhenDowned>False</playWhenDowned>
@ -13,27 +13,27 @@
<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
<keyframes>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<offset>(0, -1, 0)</offset>
<offset>(1, -1, 0)</offset>
<tick>0</tick>
<angle>23</angle>
<angle>0</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<offset>(0, -1, 0)</offset>
<tick>6</tick>
<angle>-5</angle>
<tick>20</tick>
<angle>0</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<offset>(-1, -1, 0)</offset>
<tick>30</tick>
<angle>0</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<offset>(0, -1, 0)</offset>
<tick>12</tick>
<angle>4</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<offset>(0, -1, 0)</offset>
<tick>188</tick>
<angle>-1</angle>
<tick>40</tick>
<angle>0</angle>
<rotation>North</rotation>
</li>
</keyframes>
@ -51,16 +51,16 @@
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>6</tick>
<angle>-5</angle>
<angle>0</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>12</tick>
<angle>4</angle>
<angle>0</angle>
<rotation>North</rotation>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>128</tick>
<tick>18</tick>
<angle>0</angle>
<rotation>North</rotation>
</li>

View file

@ -4,13 +4,6 @@
<defName>TestGroupAnimation1</defName>
<numActors>2</numActors>
<animationStages>
<li Class="Rimworld_Animations.AnimationStage_TicksDuration">
<ticks>200</ticks>
<animationDefs>
<li>TestAnimation1</li>
<li>TestAnimation2</li>
</animationDefs>
</li>
<li Class="Rimworld_Animations.AnimationStage_LoopRandomSelectChance">
<loops>10</loops>
<loopOptions>
@ -89,6 +82,12 @@
</li>
</contexts>
<!--
<OffsetDefs>
<li>GroinToAppropriateHeight</li>
<li>GroinToAppropriateHeight</li>
</OffsetDefs>
-->
</Rimworld_Animations.GroupAnimationDef>
</Defs>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<Rimworld_Animations.OffsetDef>
<defName>GroinToAppropriateHeight</defName>
<bodyTypeOffsets>
<li>
<bodyType>Male</bodyType>
<offset>(0, 0, 0.5)</offset>
</li>
</bodyTypeOffsets>
<raceOffsets>
<li Class="Rimworld_Animations.RaceOffsets_BodyTypes" MayRequire="erdelf.HumanoidAlienRaces,Minecraft.Mod">
<def>MinecraftCreeper</def>
<genderGraphics>
<Female>
<Hulk>(0, 0, 0.5)</Hulk>
<Thin>(0, 0, 0.3)</Thin>
</Female>
<Male>
<Hulk>(0, 0, 0.5)</Hulk>
<Thin>(0, 0, 0.3)</Thin>
</Male>
</genderGraphics>
</li>
<li Class="Rimworld_Animations.RaceOffsets_DifferentBodyTypes" MayRequire="Specific.Mod,Other.Cool.Mod">
<def>MinecraftPig</def>
<bodyType>Male</bodyType>
<offset>(0, 0, -0.5)</offset>
</li>
<li Class="Rimworld_Animations.RaceOffsets_DifferentBodyTypes" MayRequire="Specific.Minecraft.Mod">
<def>MinecraftPig</def>
<bodyType>Female</bodyType>
<offset>(0, 0, -0.5)</offset>
</li>
</raceOffsets>
</Rimworld_Animations.OffsetDef>
</Defs>

View file

@ -15,6 +15,12 @@ namespace Rimworld_Animations
{
}
public override Vector3 OffsetAtTick(int tick, PawnDrawParms parms)
{
//Todo: Use this for bodyoffsets
return base.OffsetAtTick(tick, parms);
}
//use extendedkeyframes to determine addon facing

View file

@ -11,7 +11,121 @@ using Verse.Sound;
namespace Rimworld_Animations {
public class CompExtendedAnimator : ThingComp
{
List<Tuple<int, AnimationDef>> animationQueue;
// CompExtendedAnimator
// Helps manage AnimationQueue, AbsolutePosition
private List<AnimationDef> animationQueue;
private BaseExtendedAnimatorAnchor anchor;
private bool isAnimating = false;
public bool IsAnimating
{
get
{
return isAnimating;
}
}
public bool IsAnchored
{
get
{
return anchor != null;
}
}
public Vector3 getAnchor()
{
return anchor.getDrawPos();
}
//ticks of current animation
private int animationTicks;
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();
}
}
}
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;
pawn.Drawer.renderer.SetAnimation(null);
}
public void PlayGroupAnimation(List<AnimationDef> groupAnimation)
{
animationQueue = groupAnimation;
PlayNextAnimation();
}
public void PlayGroupAnimation(List<AnimationDef> groupAnimation, BaseExtendedAnimatorAnchor anchor)
{
this.anchor = anchor;
animationQueue = groupAnimation;
PlayNextAnimation();
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look<bool>(ref this.isAnimating, "animations_isAnimating", false);
Scribe_Collections.Look<AnimationDef>(ref animationQueue, "animations_queue");
Scribe_Deep.Look<BaseExtendedAnimatorAnchor>(ref this.anchor, "animations_anchor");
}
private Pawn pawn => base.parent as Pawn;
}

View file

@ -12,6 +12,7 @@ namespace Rimworld_Animations {
public CompProperties_ExtendedAnimator()
{
base.compClass = typeof(CompExtendedAnimator);
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations
{
public abstract class BaseExtendedAnimatorAnchor : IExposable
{
public BaseExtendedAnimatorAnchor() { }
public virtual void ExposeData() { }
public abstract Vector3 getDrawPos();
public string GetUniqueLoadID()
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations
{
public class ExtendedAnimatorAnchor_Thing : BaseExtendedAnimatorAnchor
{
private Thing thing;
public ExtendedAnimatorAnchor_Thing() : base() { }
public ExtendedAnimatorAnchor_Thing(Thing thing) : base()
{
this.thing = thing;
}
public override Vector3 getDrawPos()
{
return thing.DrawPos;
}
public override void ExposeData()
{
base.ExposeData();
Scribe_References.Look<Thing>(ref this.thing, "animations_anchor_thing", false);
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations
{
public class ExtendedAnimatorAnchor_Vector3 : BaseExtendedAnimatorAnchor
{
public ExtendedAnimatorAnchor_Vector3() : base() { }
private Vector3 position;
public ExtendedAnimatorAnchor_Vector3(Vector3 position) : base()
{
this.position = position;
}
public override Vector3 getDrawPos()
{
return position;
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look<Vector3>(ref position, "animations_anchor_position", Vector3.zero);
}
}
}

View file

@ -11,6 +11,7 @@ namespace Rimworld_Animations
{
public int actorShift = 0;
public abstract bool CanAnimationBeUsed(List<Pawn> actors, out int reorder);
public abstract string DebugMessage();
//cool class for designating contexts for animations
// configure CanAnimationBeUsed to test whether it can be used

View file

@ -17,7 +17,6 @@ namespace Rimworld_Animations
public override bool CanAnimationBeUsed(List<Pawn> actors, out int reorder)
{
Log.Message("Testing this animation");
JobDriver_SexBaseInitiator latestSexBaseInitiator = (actors.FindLast(x => x.jobs?.curDriver is JobDriver_SexBaseInitiator).jobs.curDriver as JobDriver_SexBaseInitiator);
reorder = base.actorShift;
@ -25,5 +24,11 @@ namespace Rimworld_Animations
return interactionDefs.Contains(latestSexBaseInitiator.Sexprops.dictionaryKey);
}
public override string DebugMessage()
{
return "Checking for RJWSex AnimationContext\n"
+ "InteractionDefs: " + interactionDefs;
}
}
}

View file

@ -16,6 +16,11 @@ namespace Rimworld_Animations
public bool canAnimationBeUsed(List<Pawn> actors, out int reorder)
{
if (AnimationSettings.debugMode)
{
Log.Message("[anims] Checking if " + defName + " is valid animation");
}
foreach (BaseGroupAnimationContext context in contexts)
{
@ -28,5 +33,20 @@ namespace Rimworld_Animations
reorder = 0;
return false;
}
public List<AnimationDef> GetAllAnimationsForActor(int actor, int seed, int reorder = 0)
{
List<AnimationDef> animations = new List<AnimationDef>();
int actorNumber = (actor + reorder) % numActors;
foreach (AnimationStage stage in animationStages)
{
//add all new animations to list of animations
animations.AddRange(stage.GetAnimations(actorNumber, seed));
}
return animations;
}
}
}

View file

@ -11,7 +11,7 @@ namespace Rimworld_Animations
{
//Return a list containing a tuple; int for how long the animation should play for
public abstract List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed);
public abstract List<AnimationDef> GetAnimations(int actorNumber, int seed);
}
}

View file

@ -10,9 +10,9 @@ namespace Rimworld_Animations
public class AnimationStage_Branch : AnimationStage
{
public List<AnimationStage> paths;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
public override List<AnimationDef> GetAnimations(int actorNumber, int seed)
{
return paths[(seed * 59) % paths.Count].GetAnimations(actor, seed);
return paths[(seed * 59) % paths.Count].GetAnimations(actorNumber, seed);
}
}
}

View file

@ -12,48 +12,50 @@ namespace Rimworld_Animations
public int loops;
public List<AnimationLoopOption> loopOptions;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
public override List<AnimationDef> GetAnimations(int actorNumber, int seed)
{
int numberOfActors = loopOptions[0].animationDefs.Count;
List<Tuple<int, AnimationDef>> animations = new List<Tuple<int, AnimationDef>>();
List<AnimationDef> animations = new List<AnimationDef>();
for (int i = 0; i < loops; i++)
{
AnimationLoopOption option = getAnimationLoopOptionByWeight(seed + i, out int longestAnimLength);
Tuple<int, AnimationDef> animation = Tuple.Create(longestAnimLength, option.animationDefs[actor]);
animations.Append(animation);
AnimationLoopOption option = getAnimationLoopOptionByWeight(seed + i);
animations.Add(option.animationDefs[actorNumber]);
}
return animations;
}
public class AnimationLoopOption
{
public int probability;
public List<AnimationDef> animationDefs;
}
//select random element from loop options by weight; also calculate the longest anim length
public AnimationLoopOption getAnimationLoopOptionByWeight(int seed, out int longestAnimLength)
private AnimationLoopOption getAnimationLoopOptionByWeight(int seed)
{
int totalWeight = loopOptions.Sum(x => x.probability);
int randomNumber = (seed * 56) % totalWeight;
int randomNumber = ((seed * 59) % totalWeight) + 1;
int cumulativeWeight = 0;
foreach(AnimationLoopOption option in loopOptions) {
cumulativeWeight += option.probability;
for (int i = 0; i < loopOptions.Count; i++) {
cumulativeWeight += loopOptions[i].probability;
//random number is same for all pawns because they all have the same seed
if (randomNumber <= cumulativeWeight)
{
longestAnimLength = option.animationDefs.Max(x => x.durationTicks);
return option;
return loopOptions[i];
}
}
longestAnimLength = loopOptions[0].animationDefs.Max(x => x.durationTicks);
return loopOptions[0];
//default
return loopOptions.Last();
}
}
public class AnimationLoopOption
{
public int probability;
public List<AnimationDef> animationDefs;
}
}

View file

@ -7,14 +7,17 @@ using Verse;
namespace Rimworld_Animations
{
/* don't use? just use looprandomselect once
public class AnimationStage_TicksDuration : AnimationStage
{
int ticks;
List<AnimationDef> animationDefs;
public int ticks;
public List<AnimationDef> animationDefs;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
{
return new List<Tuple<int, AnimationDef>>() { Tuple.Create(ticks, animationDefs[actor]) };
}
}
*/
}

View file

@ -42,10 +42,9 @@ namespace Rimworld_Animations {
List<Pawn> participants = partnerSexBaseReceiver.parteners.Append(partner).ToList();
GroupAnimationDef groupAnimation = AnimationUtility.FindGroupAnimation(participants, out int reorder);
if (groupAnimation != null)
{
AnimationUtility.StartGroupAnimation(participants, groupAnimation, reorder);
AnimationUtility.StartGroupAnimation(participants, groupAnimation, reorder, partner);
}

View file

@ -18,26 +18,28 @@ namespace Rimworld_Animations
{
if (__instance.AnimationWorker is AnimationWorker_KeyframesExtended extendedAnimWorker)
{
//INVIS IF ANIM CALLS FOR IT
//replace maybe?
if (!extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick))
{
__instance.requestRecache = true;
return false;
}
// HEAD ROTATION ADJUST FACING get rotated textures
// ADJUST FACING get rotated textures
// compare the previous tick to the current tick; if the current tick rotation is different, recache
Rot4 animFacing = extendedAnimWorker.facingAtTick(__instance.tree.AnimationTick);
if (parms.facing != animFacing)
if (extendedAnimWorker.facingAtTick(__instance.tree.AnimationTick - 1) != extendedAnimWorker.facingAtTick(__instance.tree.AnimationTick))
{
//requestRecache or else it won't update properly
__instance.requestRecache = true;
parms.facing = animFacing;
}
//INVIS IF ANIM CALLS FOR IT
//replace maybe?
//cheaper call now comparing prev tick to cur tick
if (extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick - 1) != extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick))
{
__instance.requestRecache = true;
return extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick);
}
}
return true;

View file

@ -28,6 +28,7 @@ namespace Rimworld_Animations
&& node.tree.rootNode.AnimationWorker is AnimationWorker_KeyframesExtended rootNodeAnimationWorker)
{
// this is only for the Vector3 position to work right
parms.facing = rootNodeAnimationWorker.facingAtTick(node.tree.AnimationTick);
}
}

View file

@ -9,23 +9,18 @@ namespace Rimworld_Animations {
public static class HarmonyPatch_Pawn_DrawTracker {
public static bool Prefix(ref Pawn ___pawn, ref Vector3 __result) {
// If animating and is sexbaseinitiator jobdriver,
if (___pawn?.Drawer?.renderer?.renderTree?.rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended
&& ___pawn.jobs?.curDriver is JobDriver_SexBaseInitiator sexdriver)
CompExtendedAnimator animator = ___pawn.TryGetComp<CompExtendedAnimator>();
//align pos on top of partner, position, etc., based on animatoranchor
if (animator != null && animator.IsAnchored)
{
//align pos on top of partner
if (sexdriver?.Partner?.Drawer?.DrawPos != null)
__result = sexdriver.Partner.Drawer.DrawPos;
__result = animator.getAnchor();
return false;
}
return true;
}
}
}

View file

@ -29,7 +29,7 @@ namespace Rimworld_Animations {
Scribe_Values.Look(ref orgasmQuiver, "RJWAnimations-orgasmQuiver");
Scribe_Values.Look(ref fastAnimForQuickie, "RJWAnimations-fastAnimForQuickie");
Scribe_Values.Look(ref rapeShiver, "RJWAnimations-rapeShiver");
Scribe_Values.Look(ref hearts, "RJWAnimation-sheartsOnLovin");
Scribe_Values.Look(ref hearts, "RJWAnimation-heartsOnLovin");
Scribe_Values.Look(ref PlayAnimForNonsexualActs, "RJWAnims-PlayAnimForNonsexualActs");
Scribe_Values.Look(ref applySemenOnAnimationOrgasm, "RJWAnimations-applySemenOnOrgasm", false);
Scribe_Values.Look(ref soundOverride, "RJWAnimations-rjwAnimSoundOverride", true);

View file

@ -21,15 +21,42 @@ namespace Rimworld_Animations {
participants[1].Drawer.renderer.SetAnimation(AnimationDefOf.TestAnimation2);
}
//startgroupanimator with anchor
//don't anchor to self if anchor is self
public static void StartGroupAnimation(List<Pawn> participants, GroupAnimationDef groupAnimationDef, int reorder, Thing anchor)
{
int seed = GenTicks.TicksGame;
for (int i = 0; i < participants.Count; i++)
{
if (anchor is Pawn pawn && pawn == participants[i])
{
List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn);
}
else
{
//each participant gets their own unique extendedanimatoranchor, important for scribe_deep saving
List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
BaseExtendedAnimatorAnchor animatorAnchor = new ExtendedAnimatorAnchor_Thing(anchor);
participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn, animatorAnchor);
}
}
}
//startgroupanimation without anchor; just play where standing
public static void StartGroupAnimation(List<Pawn> participants, GroupAnimationDef groupAnimationDef, int reorder)
{
for(int i = 0; i < participants.Count; i++)
int seed = GenTicks.TicksGame;
for (int i = 0; i < participants.Count; i++)
{
//todo: pass all animationstages to ExtendedAnimator, and queue animations
participants[i].Drawer.renderer.SetAnimation(
groupAnimationDef.animationStages[0]
.GetAnimations((i + reorder) % participants.Count, GenTicks.TicksGame)[0].Item2);
List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn);
}
}
@ -38,13 +65,13 @@ namespace Rimworld_Animations {
{
foreach(Pawn pawn in participants)
{
pawn.Drawer.renderer.SetAnimation(null);
pawn.TryGetComp<CompExtendedAnimator>()?.StopAnimating();
}
}
public static void StopGroupAnimation(Pawn participant)
{
participant?.Drawer?.renderer?.SetAnimation(null);
participant.TryGetComp<CompExtendedAnimator>()?.StopAnimating();
}
public static GroupAnimationDef FindGroupAnimation(List<Pawn> participants, out int reorder)
@ -55,8 +82,6 @@ namespace Rimworld_Animations {
int reorder2 = 0;
Log.Message(DefDatabase<GroupAnimationDef>.AllDefsListForReading[0].defName);
DefDatabase<GroupAnimationDef>.AllDefsListForReading.TryRandomElement((GroupAnimationDef x) =>
x.canAnimationBeUsed(participants, out reorder2), out GroupAnimationDef result);

View file

@ -77,6 +77,9 @@
<Compile Include="1.5\Source\Comps\CompProperties_ExtendedAnimator.cs" />
<Compile Include="1.5\Source\Comps\CompProperties_ThingAnimator.cs" />
<Compile Include="1.5\Source\Comps\CompThingAnimator.cs" />
<Compile Include="1.5\Source\Comps\ExtendedAnimatorAnchor\BaseExtendedAnimatorAnchor.cs" />
<Compile Include="1.5\Source\Comps\ExtendedAnimatorAnchor\ExtendedAnimatorAnchor_Thing.cs" />
<Compile Include="1.5\Source\Comps\ExtendedAnimatorAnchor\ExtendedAnimatorAnchor_Vector3.cs" />
<Compile Include="1.5\Source\Defs\AnimationDefOf.cs" />
<Compile Include="1.5\Source\GroupAnimations\GroupAnimationContexts\BaseGroupAnimationContext.cs" />
<Compile Include="1.5\Source\GroupAnimations\GroupAnimationContexts\GroupAnimationContext_RJWSex.cs" />
@ -114,6 +117,7 @@
<Content Include="1.5\Defs\AnimationDefs\TestAnimation2.xml" />
<Content Include="1.5\Defs\GroupAnimationDefs\TestGroupAnimation1.xml" />
<Content Include="1.5\Defs\MainTabDefs\MainButtonDef.xml" />
<Content Include="1.5\Defs\OffsetDefs\OffsetDef_GroinToAppropriateHeight.xml" />
<Content Include="1.5\Defs\SoundDefs\Sounds_Sex.xml" />
<Content Include="1.5\Patches\AnimationPatchHSK.xml" />
<Content Include="1.5\Patches\AnimationPatch_CompExtendedAnimator.xml" />