diff --git a/1.6/Assemblies/Rimworld-Animations.dll b/1.6/Assemblies/Rimworld-Animations.dll
index a52d4f5..cf6a166 100644
Binary files a/1.6/Assemblies/Rimworld-Animations.dll and b/1.6/Assemblies/Rimworld-Animations.dll differ
diff --git a/1.6/Defs/GroupAnimationDefs/Cunnilingus/GroupAnimation_Cunnilingus.xml b/1.6/Defs/GroupAnimationDefs/Cunnilingus/GroupAnimation_Cunnilingus.xml
index a7e6f5a..68d4e4b 100644
--- a/1.6/Defs/GroupAnimationDefs/Cunnilingus/GroupAnimation_Cunnilingus.xml
+++ b/1.6/Defs/GroupAnimationDefs/Cunnilingus/GroupAnimation_Cunnilingus.xml
@@ -82,7 +82,11 @@
1
-
+
+
+
+
+
@@ -95,18 +99,14 @@
-
-
+
1
-
-
-
-
+
@@ -117,6 +117,12 @@
+
+
+
+
+
+
@@ -125,6 +131,15 @@
0
+
+
+
+
+
+
+
+
+
@@ -132,12 +147,6 @@
-
-
-
-
-
-
diff --git a/1.6/Patches/AnimationPatch_PawnRenderTree_OffsetSubWorker.xml b/1.6/Patches/AnimationPatch_PawnRenderTree_OffsetSubWorker.xml
index 6aa36a8..a12b7b6 100644
--- a/1.6/Patches/AnimationPatch_PawnRenderTree_OffsetSubWorker.xml
+++ b/1.6/Patches/AnimationPatch_PawnRenderTree_OffsetSubWorker.xml
@@ -1,20 +1,16 @@
-
+
Always
-
- /Defs/PawnRenderTreeDef/root/subworkerClasses
+
Always
-
- /Defs/PawnRenderTreeDef/root
-
-
-
-
+ /Defs/PawnRenderTreeDef/root[not(subworkerClasses)]
+
+
+
-
/Defs/PawnRenderTreeDef/root/subworkerClasses
@@ -23,5 +19,4 @@
-
\ No newline at end of file
diff --git a/1.6/Source/Animations/GroupAnimations/GroupAnimationContexts/GroupAnimationContext.cs b/1.6/Source/Animations/GroupAnimations/GroupAnimationContexts/GroupAnimationContext.cs
index 74d91da..0cb13b6 100644
--- a/1.6/Source/Animations/GroupAnimations/GroupAnimationContexts/GroupAnimationContext.cs
+++ b/1.6/Source/Animations/GroupAnimations/GroupAnimationContexts/GroupAnimationContext.cs
@@ -91,7 +91,7 @@ namespace Rimworld_Animations
public List FindAnyWorkingSet(List actors)
{
- if (actors.Count != whitelist.Count) return null;
+ if (whitelist.NullOrEmpty() || actors.Count != whitelist.Count) return null;
int numActors = actors.Count;
@@ -170,7 +170,7 @@ namespace Rimworld_Animations
return false;
}
- if (!blacklist.NullOrEmpty() && blacklist[index].PawnTest(actor))
+ if (!blacklist.NullOrEmpty() && index < blacklist.Count && blacklist[index] != null && blacklist[index].PawnTest(actor))
{
return false;
}
diff --git a/1.6/Source/Animations/GroupAnimations/GroupAnimationDef.cs b/1.6/Source/Animations/GroupAnimations/GroupAnimationDef.cs
index 30c9f61..c314bef 100644
--- a/1.6/Source/Animations/GroupAnimations/GroupAnimationDef.cs
+++ b/1.6/Source/Animations/GroupAnimations/GroupAnimationDef.cs
@@ -167,7 +167,9 @@ namespace Rimworld_Animations
string bodyTypeDef = (pawn.story?.bodyType != null) ? pawn.story.bodyType.ToString() : "";
- if (offsetDefs[actor].FindOffset(pawn, out BaseAnimationOffset animationOffset))
+ //defs without offsetDefs (or fewer than numActors) just get no offset
+ if (!offsetDefs.NullOrEmpty() && actor < offsetDefs.Count && offsetDefs[actor] != null
+ && offsetDefs[actor].FindOffset(pawn, out BaseAnimationOffset animationOffset))
{
position = animationOffset.getOffset(pawn);
rotation = animationOffset.getRotation(pawn);
diff --git a/1.6/Source/Comps/CompExtendedAnimator.cs b/1.6/Source/Comps/CompExtendedAnimator.cs
index 17ce3a3..fe71c2a 100644
--- a/1.6/Source/Comps/CompExtendedAnimator.cs
+++ b/1.6/Source/Comps/CompExtendedAnimator.cs
@@ -26,6 +26,12 @@ namespace Rimworld_Animations {
private VoiceDef voice;
private VoiceTagDef lastVoiceTag;
+ //dead actors (corpses) in this pawn's animation; corpses only TickRare, so this comp ticks their animators for them
+ private List drivenPawns;
+
+ //the renderer's current animation isn't saved by vanilla; reapply it on the first tick after loading
+ private bool restoreAnimationAfterLoad;
+
private bool isAnimating = false;
@@ -41,7 +47,7 @@ namespace Rimworld_Animations {
{
get
{
- return anchor != null;
+ return anchor != null && anchor.IsValid;
}
}
@@ -135,6 +141,42 @@ namespace Rimworld_Animations {
public override void CompTick()
{
+ AnimatorTick();
+
+ if (!drivenPawns.NullOrEmpty())
+ {
+ foreach (Pawn drivenPawn in drivenPawns.ToList())
+ {
+ CompExtendedAnimator drivenAnimator = drivenPawn?.TryGetComp();
+
+ //stop driving if the corpse finished, was destroyed or got resurrected (it ticks itself again)
+ if (drivenAnimator == null || !drivenAnimator.IsAnimating || !drivenPawn.Dead)
+ {
+ drivenPawns.Remove(drivenPawn);
+ continue;
+ }
+
+ drivenAnimator.AnimatorTick();
+ }
+ }
+
+ base.CompTick();
+ }
+
+ public void AddDrivenPawn(Pawn deadPawn)
+ {
+ if (drivenPawns == null) drivenPawns = new List();
+ drivenPawns.AddDistinct(deadPawn);
+ }
+
+ public void AnimatorTick()
+ {
+ if (restoreAnimationAfterLoad)
+ {
+ restoreAnimationAfterLoad = false;
+ RestoreAnimationAfterLoad();
+ }
+
if (isAnimating)
{
animationTicks++;
@@ -157,10 +199,6 @@ namespace Rimworld_Animations {
CheckAndPlaySounds();
}
-
-
-
- base.CompTick();
}
//returns false if still more animations
@@ -186,8 +224,33 @@ namespace Rimworld_Animations {
}
}
+ private void RestoreAnimationAfterLoad()
+ {
+ if (!isAnimating || animationQueue.NullOrEmpty()) return;
+
+ PawnRenderer renderer = pawn.Drawer?.renderer;
+ if (renderer?.renderTree == null) return;
+
+ renderer.SetAnimation(animationQueue[0]);
+
+ //resume partway through the current animation instead of from its first frame
+ renderer.renderTree.animationStartTick = GenTicks.TicksGame - animationTicks;
+ renderer.SetAllGraphicsDirty();
+ }
+
public void StopAnimating()
{
+ //stop any corpses this pawn was animating, since they can't stop themselves
+ if (!drivenPawns.NullOrEmpty())
+ {
+ List pawnsToStop = drivenPawns;
+ drivenPawns = null;
+ foreach (Pawn drivenPawn in pawnsToStop)
+ {
+ drivenPawn?.TryGetComp()?.StopAnimating();
+ }
+ }
+
isAnimating = false;
animationQueue = null;
anchor = null;
@@ -203,6 +266,7 @@ namespace Rimworld_Animations {
this.actorIndex = actorIndex;
this.currentGroupAnimation = groupAnimationDef;
+ drivenPawns = null;
ticksSinceLastVoicePlayed = GenTicks.TicksGame;
ticksUntilNextVoice = 0;
@@ -236,9 +300,23 @@ namespace Rimworld_Animations {
Scribe_Collections.Look(ref animationQueue, "animations_queue");
Scribe_Defs.Look(ref currentGroupAnimation, "animations_groupAnimationDef");
Scribe_Deep.Look(ref this.anchor, "animations_anchor");
+ //dead pawns count as destroyed, so their references must be saved with saveDestroyedThings or they load as null
+ Scribe_Collections.Look(ref this.drivenPawns, "animations_drivenPawns", true, LookMode.Reference);
Scribe_Values.Look(ref this.offset, "animations_offset", Vector3.zero);
Scribe_Values.Look(ref this.rotation, "animations_rotationOffset", 0);
Scribe_Defs.Look(ref this.voice, "animations_voice");
+
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ drivenPawns?.RemoveAll(x => x == null);
+
+ if (isAnimating)
+ {
+ //animation defs can go missing between saves (e.g. an anim pack was removed)
+ if (animationQueue.NullOrEmpty()) isAnimating = false;
+ else restoreAnimationAfterLoad = true;
+ }
+ }
}
@@ -311,123 +389,79 @@ namespace Rimworld_Animations {
public void CheckAndPlaySounds()
{
+ //use held position/map so it also works for pawns inside corpses
+ Map map = pawn.MapHeld;
+ if (map == null) return;
PawnRenderNode rootNode = pawn.Drawer?.renderer?.renderTree?.rootNode;
- //check if the rootnode has sounds; if so play it
- if (rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)
+ //check the rootnode for sounds
+ if (rootNode != null)
{
- SoundDef sound = animWorker.soundAtTick(rootNode.tree.AnimationTick, rootNode.tree.currentAnimation, rootNode);
+ PlaySoundsForNode(rootNode, map);
+ }
+
+ //do the same for all the child nodes
+ if (rootNode?.children != null)
+ {
+ foreach (PawnRenderNode node in rootNode.children)
+ {
+ if (node != null) PlaySoundsForNode(node, map);
+ }
+ }
+ }
+
+ private void PlaySoundsForNode(PawnRenderNode node, Map map)
+ {
+ if (!(node.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)) return;
+
+ if (RJWAnimationSettings.playSounds)
+ {
+ SoundDef sound = animWorker.soundAtTick(node.tree.AnimationTick, node.tree.currentAnimation, node);
if (sound != null)
{
- SoundInfo soundInfo = new TargetInfo(pawn.Position, pawn.Map);
+ SoundInfo soundInfo = new TargetInfo(pawn.PositionHeld, 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)
+ //corpses don't talk
+ if (!RJWAnimationSettings.playVoices || pawn.Dead) return;
+
+ //play voice sounds
+ VoiceTagDef voiceTag = animWorker.voiceAtTick(node.tree.AnimationTick, node.tree.currentAnimation, node);
+ if (voiceTag == null
+ || (lastVoiceTag == voiceTag && ticksSinceLastVoicePlayed + ticksUntilNextVoice >= GenTicks.TicksGame)) //play a new voice tag, or wait until time elapsed
{
- foreach (PawnRenderNode node in rootNode?.children)
+ return;
+ }
+
+ lastVoiceTag = voiceTag;
+ ticksSinceLastVoicePlayed = GenTicks.TicksGame;
+
+ SoundInfo voiceInfo = new TargetInfo(pawn.PositionHeld, map);
+ voiceInfo.volumeFactor = RJWAnimationSettings.voicesVolume;
+
+ 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);
+ if (pawnDefaultVoice.sounds.ContainsKey(voiceTag))
{
- 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);
- }
- }
- }
- }
- }
+ ticksUntilNextVoice = pawnDefaultVoice.ticksBetweenPlays.RandomInRange;
+ pawnDefaultVoice.sounds[voiceTag].PlayOneShot(voiceInfo);
}
}
- //do the same for all the child nodes
-
}
diff --git a/1.6/Source/Comps/ExtendedAnimatorAnchor/BaseExtendedAnimatorAnchor.cs b/1.6/Source/Comps/ExtendedAnimatorAnchor/BaseExtendedAnimatorAnchor.cs
index 6116a8c..8cf28af 100644
--- a/1.6/Source/Comps/ExtendedAnimatorAnchor/BaseExtendedAnimatorAnchor.cs
+++ b/1.6/Source/Comps/ExtendedAnimatorAnchor/BaseExtendedAnimatorAnchor.cs
@@ -15,6 +15,9 @@ namespace Rimworld_Animations
public virtual void ExposeData() { }
public abstract Vector3 getDrawPos();
+ //false if the anchor can't be drawn at anymore (e.g. the thing was destroyed)
+ public virtual bool IsValid => true;
+
public string GetUniqueLoadID()
{
throw new NotImplementedException();
diff --git a/1.6/Source/Comps/ExtendedAnimatorAnchor/ExtendedAnimatorAnchor_Thing.cs b/1.6/Source/Comps/ExtendedAnimatorAnchor/ExtendedAnimatorAnchor_Thing.cs
index 6f86334..bd1fa4b 100644
--- a/1.6/Source/Comps/ExtendedAnimatorAnchor/ExtendedAnimatorAnchor_Thing.cs
+++ b/1.6/Source/Comps/ExtendedAnimatorAnchor/ExtendedAnimatorAnchor_Thing.cs
@@ -26,6 +26,9 @@ namespace Rimworld_Animations
this.thing = thing;
}
+ //spawned check also covers destroyed things, and things that didn't come back after loading
+ public override bool IsValid => thing != null && thing.Spawned;
+
public override Vector3 getDrawPos()
{
//x and z position, regular altitude for pawns
diff --git a/1.6/Source/MainTabWindows/MainTabWindow_OffsetConfigure.cs b/1.6/Source/MainTabWindows/MainTabWindow_OffsetConfigure.cs
index 318e9c2..4b2878a 100644
--- a/1.6/Source/MainTabWindows/MainTabWindow_OffsetConfigure.cs
+++ b/1.6/Source/MainTabWindows/MainTabWindow_OffsetConfigure.cs
@@ -58,13 +58,20 @@ namespace Rimworld_Animations {
if (listingStandard.ButtonText("RimAnims_Reset".Translate()))
{
- if (!RJWAnimationSettings.offsets.NullOrEmpty()
- && RJWAnimationSettings.offsets.ContainsKey(AnimationUtility.OffsetLookupKey(curPawn)))
- {
- RJWAnimationSettings.offsets[AnimationUtility.OffsetLookupKey(curPawn)].Reset();
- }
- posX = posY = posZ = rot = 0;
-
+ //remove the saved override entirely; an entry left behind would keep overriding the def's offset with zeros
+ RJWAnimationSettings.offsets?.Remove(AnimationUtility.OffsetLookupKey(curPawn));
+
+ //go back to the offset from the animation's offsetDefs
+ extendedAnimator.CurrentGroupAnimation.GetOffset(extendedAnimator.ActorIndex, curPawn, out Vector3? defOffset, out int? defRotation);
+ extendedAnimator.Offset = defOffset;
+ extendedAnimator.Rotation = defRotation;
+
+ Vector3 resetOffset = defOffset ?? Vector3.zero;
+ posX = resetOffset.x;
+ posY = resetOffset.y;
+ posZ = resetOffset.z;
+ rot = defRotation ?? 0;
+
}
listingStandard.GapLine();
diff --git a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Masturbate.cs b/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Masturbate.cs
deleted file mode 100644
index d7c9e98..0000000
--- a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Masturbate.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using HarmonyLib;
-using rjw;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Verse;
-using Verse.AI;
-
-namespace Rimworld_Animations
-{
- [HarmonyPatch(typeof(JobDriver_Masturbate), "SetupDurationTicks")]
- public class HarmonyPatch_JobDriver_Masturbate
- {
- public static void Postfix(JobDriver_Masturbate __instance)
- {
- //prevent early stoppage of masturbate jobdriver during animation
-
- /* not needed anymore? Ticks are assigned during animation
-
- __instance.duration = 10000000;
- __instance.ticks_left = __instance.duration;
-
- */
-
- }
- }
-
- [HarmonyPatch(typeof(JobDriver_Masturbate), "MakeNewToils")]
- public class HarmonyPatch_JobDriver_Masturbate2
- {
- public static void Postfix(JobDriver_Masturbate __instance, ref IEnumerable __result)
- {
-
- var toils = __result.ToList();
-
- //sex toil
- toils[1].initAction += delegate ()
- {
- CompExtendedAnimator pawnAnimator = __instance.pawn.TryGetComp();
-
- // if pawn was given an animation to play,
- if (pawnAnimator.IsAnimating)
- {
- //set duration of masturbate toil to anim length
- toils[1].defaultDuration = pawnAnimator.AnimationLength;
- }
-
- };
-
- __result = toils.AsEnumerable();
- }
- }
-}
diff --git a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Sex.cs b/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Sex.cs
index 622b7c1..d8d3ae2 100644
--- a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Sex.cs
+++ b/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Sex.cs
@@ -30,36 +30,57 @@ namespace Rimworld_Animations
public static void Postfix(JobDriver_Sex __instance, Pawn pawn, Thing target)
{
//if neverending sex and pawn doesn't have an animation,
- if (__instance.neverendingsex
- && !pawn.TryGetComp().IsAnimating)
+ //only retry about once a second; searching every animation def every tick is expensive when none fit
+ if (!__instance.neverendingsex
+ || !pawn.IsHashIntervalTick(60)
+ || !(pawn.TryGetComp() is CompExtendedAnimator animator)
+ || animator.IsAnimating)
{
-
- //start a new animation for all the pawns paired with receiver job driver
- List participants;
- if (target is Pawn receiverPawn)
- {
- participants = (receiverPawn?.jobs?.curDriver as JobDriver_SexBaseReciever)?.parteners;
- if (!participants.NullOrEmpty() && !participants.Contains(receiverPawn))
- {
- participants = participants.Append(receiverPawn).ToList();
- }
- }
- else
- {
- participants = new List { pawn };
- }
-
-
- GroupAnimationDef animation = AnimationUtility.FindGroupAnimation(participants, out List participantsOrdered);
- if (animation != null)
- {
- Thing anchor = (Thing)__instance.Bed ?? pawn;
- AnimationUtility.StartGroupAnimation(participantsOrdered, animation, anchor);
- }
-
-
+ return;
}
+ //start a new animation for all the pawns paired with receiver job driver
+ List participants;
+ Thing anchor = __instance.Bed;
+ if (target is Pawn receiverPawn)
+ {
+ participants = (receiverPawn.jobs?.curDriver as JobDriver_SexBaseReciever)?.parteners?.ToList();
+ if (participants.NullOrEmpty()) return;
+
+ if (!participants.Contains(receiverPawn))
+ {
+ participants.Add(receiverPawn);
+ }
+ anchor = anchor ?? receiverPawn;
+ }
+ else if (target is Corpse corpse && corpse.InnerPawn != null)
+ {
+ participants = new List { pawn, corpse.InnerPawn };
+ anchor = anchor ?? corpse;
+ }
+ else
+ {
+ participants = new List { pawn };
+ anchor = anchor ?? pawn;
+ }
+
+ GroupAnimationDef animation = AnimationUtility.FindGroupAnimation(participants, out List participantsOrdered);
+ if (animation == null) return;
+
+ AnimationUtility.StartGroupAnimation(participantsOrdered, animation, anchor);
+
+ //keep rjw's timers in step with the new animation, same as when sex starts
+ int animTicks = AnimationUtility.GetAnimationLength(pawn);
+ foreach (Pawn participant in participants)
+ {
+ if (participant?.jobs?.curDriver is JobDriver_Sex participantJobDriver)
+ {
+ participantJobDriver.ticks_left = animTicks;
+ participantJobDriver.sex_ticks = animTicks;
+ participantJobDriver.orgasmStartTick = animTicks;
+ participantJobDriver.duration = animTicks;
+ }
+ }
}
}
diff --git a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_SexBaseInitiator.cs b/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_SexBaseInitiator.cs
index 36a1316..d2680ce 100644
--- a/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_SexBaseInitiator.cs
+++ b/1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_SexBaseInitiator.cs
@@ -24,6 +24,10 @@ namespace Rimworld_Animations
{
participants = new List() { pawn };
}
+ else if (partner == null)
+ {
+ return;
+ }
else if (__instance is JobDriver_ViolateCorpse)
{
participants = new List() { pawn, partner };
@@ -52,18 +56,23 @@ namespace Rimworld_Animations
if (groupAnimation != null)
{
- Thing anchor = (Thing)__instance.Bed ?? partner;
+ //anchor to the corpse itself, not its InnerPawn; a pawn inside a corpse keeps the position it died at
+ Thing anchor = (Thing)__instance.Bed ?? (Thing)(__instance.Target as Corpse) ?? partner;
AnimationUtility.StartGroupAnimation(participantsOrdered, groupAnimation, anchor);
int animTicks = AnimationUtility.GetAnimationLength(pawn);
- Log.Message("InteractionDef: " + __instance.Sexprops.dictionaryKey);
+ if (RJWAnimationSettings.debugMode)
+ {
+ Log.Message("InteractionDef: " + __instance.Sexprops?.dictionaryKey);
+ }
+
foreach (Pawn participant in participants)
{
if (RJWAnimationSettings.debugMode)
{
- Log.Message("Participant: " + participant.Name);
- Log.Message("JobDriver: " + participant.CurJobDef.defName);
+ Log.Message("Participant: " + participant?.Name);
+ Log.Message("JobDriver: " + participant?.CurJobDef?.defName);
}
//null ref check for pawns that might have lost their jobs or become null for some reason
diff --git a/1.6/Source/Patches/RJWPatches/JobDrivers/SexBaseReceivers/HarmonyPatch_JobDriver_SexBaseReceiverRaped.cs b/1.6/Source/Patches/RJWPatches/JobDrivers/SexBaseReceivers/HarmonyPatch_JobDriver_SexBaseReceiverRaped.cs
deleted file mode 100644
index 703bdcc..0000000
--- a/1.6/Source/Patches/RJWPatches/JobDrivers/SexBaseReceivers/HarmonyPatch_JobDriver_SexBaseReceiverRaped.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using HarmonyLib;
-using rjw;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Verse.AI;
-
-namespace Rimworld_Animations
-{
- [HarmonyPatch(typeof(JobDriver_SexBaseRecieverRaped), "MakeNewToils")]
- public class HarmonyPatch_JobDriver_SexBaseReceiverRaped
- {
- /*
- * Doesn't work; ienumerables are read-only, can't modify toil
- * would need to harmonypatch; stopped partner animating in sexbaseinitiator instead
- *
- public static void Postfix(JobDriver_SexBaseRecieverRaped __instance, ref IEnumerable __result)
- {
- //added for sudden end of jobdriver
- __result.Last().AddFinishAction(delegate {
- AnimationUtility.StopGroupAnimation(__instance.pawn);
- });
- }
-
- */
- }
-}
diff --git a/1.6/Source/Patches/RimworldPatches/HarmonyPatch_PawnRenderTree.cs b/1.6/Source/Patches/RimworldPatches/HarmonyPatch_PawnRenderTree.cs
index c62bcc4..3070ff7 100644
--- a/1.6/Source/Patches/RimworldPatches/HarmonyPatch_PawnRenderTree.cs
+++ b/1.6/Source/Patches/RimworldPatches/HarmonyPatch_PawnRenderTree.cs
@@ -57,7 +57,7 @@ namespace Rimworld_Animations
if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(pivot);
if (quaternion != Quaternion.identity) matrix *= Matrix4x4.Rotate(quaternion);
if (scale != Vector3.one) matrix *= Matrix4x4.Scale(scale);
- if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(scale).inverse;
+ if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(pivot).inverse;
float num = node.Worker.AltitudeFor(node, parms);
if (num != 0f)
@@ -96,7 +96,7 @@ namespace Rimworld_Animations
if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(pivot);
if (canRotate && rotation != Quaternion.identity) matrix *= Matrix4x4.Rotate(rotation);
if (canScale && scale != Vector3.one) matrix *= Matrix4x4.Scale(scale);
- if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(scale).inverse;
+ if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(pivot).inverse;
}
float num = node.Worker.AltitudeFor(node, parms);
diff --git a/1.6/Source/Patches/RimworldPatches/HarmonyPatch_Thing.cs b/1.6/Source/Patches/RimworldPatches/HarmonyPatch_Thing.cs
deleted file mode 100644
index ec738f7..0000000
--- a/1.6/Source/Patches/RimworldPatches/HarmonyPatch_Thing.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using HarmonyLib;
-using RimWorld;
-using Verse;
-
-namespace Rimworld_Animations
-{
- [HarmonyPatch(typeof(Thing), "DrawAt")]
- public static class HarmonyPatch_Thing
- {
- /*
- public static bool Prefix(Thing __instance)
- {
- CompThingAnimator thingAnimator = __instance.TryGetComp();
- if (thingAnimator != null && thingAnimator.isAnimating)
- {
- thingAnimator.AnimateThing(__instance);
- return false;
-
- }
-
- return true;
-
- }
-
-
- */
- }
-}
diff --git a/1.6/Source/Settings/PawnScaledOffsets.cs b/1.6/Source/Settings/PawnScaledOffsets.cs
index 8f280a1..38577e4 100644
--- a/1.6/Source/Settings/PawnScaledOffsets.cs
+++ b/1.6/Source/Settings/PawnScaledOffsets.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -46,7 +46,7 @@ public class PawnScaledOffsets : IExposable
{
smallestSizeOffset = largestSizeOffset = Vector3.zero;
smallestSizeRotation = largestSizeRotation = 0;
- smallestSize = largestSize = 1;
+ smallestSize = largestSize = -1; //back to uninitialized
}
public void GetOffsets(Pawn pawn, out Vector3 offset, out int rotation)
diff --git a/1.6/Source/Settings/RJWAnimationSettings.cs b/1.6/Source/Settings/RJWAnimationSettings.cs
index 2c594bc..184bb7f 100644
--- a/1.6/Source/Settings/RJWAnimationSettings.cs
+++ b/1.6/Source/Settings/RJWAnimationSettings.cs
@@ -27,21 +27,25 @@ namespace Rimworld_Animations {
{
base.ExposeData();
+
+ //some load defaults below differ from the field defaults. keys equal to their load default were never written,
+ //so a missing key in an existing file usually means the player chose that value; keep those load defaults
+ //and force-save these keys so the file is always explicit from now on
Scribe_Values.Look(ref playVoices, "RJWAnimations_PlayVoices", true);
Scribe_Values.Look(ref playSounds, "RJWAnimations_PlaySounds", true);
Scribe_Values.Look(ref playHumanlikeVoicesAsDefault, "RJWAnimations-playHumanlikeVoicesAsDefault", true);
- Scribe_Values.Look(ref maleSounds, "RJWAnimations-maleSounds", true);
- Scribe_Values.Look(ref femaleSounds, "RJWAnimations-femaleSounds", true);
+ Scribe_Values.Look(ref maleSounds, "RJWAnimations-maleSounds", true, true);
+ Scribe_Values.Look(ref femaleSounds, "RJWAnimations-femaleSounds", true, true);
Scribe_Values.Look(ref debugMode, "RJWAnimations-AnimsDebugMode", false);
- Scribe_Values.Look(ref offsetTab, "RJWAnimations-EnableOffsetTab", false);
- Scribe_Values.Look(ref hearts, "RJWAnimation-heartsOnLovin");
+ Scribe_Values.Look(ref offsetTab, "RJWAnimations-EnableOffsetTab", false, true);
+ Scribe_Values.Look(ref hearts, "RJWAnimation-heartsOnLovin", false, true);
Scribe_Values.Look(ref soundOverride, "RJWAnimations-rjwAnimSoundOverride", true);
Scribe_Values.Look(ref maleAnalCanBeFucked, "RJWAnimations-maleAnalCanBeFucked", true);
Scribe_Values.Look(ref floatRangeInRenderTreeMenu, "RJWAnimations-FloatRangeRenderMenu", 1f);
Scribe_Values.Look(ref soundVolume, "RJWAnimations-soundVolume", 1f);
Scribe_Values.Look(ref voicesVolume, "RJWAnimations-voicesVolume", 1f);
- Scribe_Values.Look(ref voicesFrequencyTicks, "RJWAnimations-voicesFrequencyTicks", 1f);
+ Scribe_Values.Look(ref voicesFrequencyTicks, "RJWAnimations-voicesFrequencyTicks", 1f, true);
Scribe_Collections.Look(ref offsets, "RJWAnimations-animationScaledOffsets", LookMode.Value, LookMode.Deep);
diff --git a/1.6/Source/Utilities/AnimationUtility.cs b/1.6/Source/Utilities/AnimationUtility.cs
index dfaddc9..5f3aa2d 100644
--- a/1.6/Source/Utilities/AnimationUtility.cs
+++ b/1.6/Source/Utilities/AnimationUtility.cs
@@ -27,7 +27,9 @@ namespace Rimworld_Animations {
for (int i = 0; i < participants.Count; i++)
{
- if (anchor is Pawn pawn && pawn == participants[i])
+ //don't anchor a pawn to itself, or a dead pawn to its own corpse
+ if ((anchor is Pawn pawn && pawn == participants[i])
+ || (anchor is Corpse corpse && corpse.InnerPawn == participants[i]))
{
participants[i].TryGetComp().PlayGroupAnimation(groupAnimationDef, null, i, seed, participants);
@@ -38,6 +40,27 @@ namespace Rimworld_Animations {
participants[i].TryGetComp().PlayGroupAnimation(groupAnimationDef, animatorAnchor, i, seed, participants);
}
}
+
+ DriveDeadParticipants(participants);
+ }
+
+ //corpses only TickRare, so their animators never advance on their own;
+ //hand them to a living participant's animator to tick
+ private static void DriveDeadParticipants(List participants)
+ {
+ Pawn driver = participants.FirstOrDefault(x => !x.Dead);
+ if (driver == null) return;
+
+ CompExtendedAnimator driverAnimator = driver.TryGetComp();
+ if (driverAnimator == null) return;
+
+ foreach (Pawn participant in participants)
+ {
+ if (participant.Dead)
+ {
+ driverAnimator.AddDrivenPawn(participant);
+ }
+ }
}
//startgroupanimation without anchor; just play where standing
@@ -49,6 +72,8 @@ namespace Rimworld_Animations {
{
participants[i].TryGetComp().PlayGroupAnimation(groupAnimationDef, i, seed, participants);
}
+
+ DriveDeadParticipants(participants);
}
@@ -67,8 +92,9 @@ namespace Rimworld_Animations {
public static GroupAnimationDef FindGroupAnimation(List participants, out List pawnOrder)
{
+ pawnOrder = null;
+ if (participants.NullOrEmpty() || participants.Contains(null)) return null;
-
/* This method is fooken expensive
List<(GroupAnimationDef, List, int Priority)> potentialAnimations = new List<(GroupAnimationDef, List, int)>();
@@ -108,13 +134,17 @@ namespace Rimworld_Animations {
}
- //Randomize the list,
- potentialAnimations.OrderBy(_ => Rand.Int);
(GroupAnimationDef, List, int) selectedAnimation;
- potentialAnimations.TryRandomElementByWeight(((GroupAnimationDef, List, int) x) => x.Item3, out selectedAnimation);
- //Find a highest priority animation, out as selectedAnimation
- // .TryMaxBy(((GroupAnimationDef, List, int) x) => x.Item3, out (GroupAnimationDef, List, int) selectedAnimation);
+ //pick randomly, weighted by priority
+ //negative priorities count as 0 so they don't log "Negative weight" errors
+ if (!potentialAnimations.TryRandomElementByWeight(((GroupAnimationDef, List, int) x) => Math.Max(x.Item3, 0), out selectedAnimation)
+ && potentialAnimations.Count > 0)
+ {
+ //every candidate has priority <= 0 (weak/fallback contexts); pick evenly between the best of them
+ int bestPriority = potentialAnimations.Max(x => x.Item3);
+ potentialAnimations.Where(x => x.Item3 == bestPriority).TryRandomElement(out selectedAnimation);
+ }
//set pawnOrder as selected animation permutation
pawnOrder = selectedAnimation.Item2;
diff --git a/About/About.xml b/About/About.xml
index a37de85..9c21172 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -8,7 +8,7 @@
1.6
c0ffee.rimworld.animations
- 2.2.0
+ 2.2.1
UI/MainTab
diff --git a/Languages/ChineseSimplified/DefInjected/MainButtonDef/MainButtonDef.xml b/Languages/ChineseSimplified/DefInjected/MainButtonDef/MainButtonDef.xml
new file mode 100644
index 0000000..da8bf6c
--- /dev/null
+++ b/Languages/ChineseSimplified/DefInjected/MainButtonDef/MainButtonDef.xml
@@ -0,0 +1,5 @@
+
+
+ 动画管理器
+ 编辑角色动画的偏移量。
+
\ No newline at end of file
diff --git a/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Cowgirl_Xray.xml b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Cowgirl_Xray.xml
new file mode 100644
index 0000000..e1991a7
--- /dev/null
+++ b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Cowgirl_Xray.xml
@@ -0,0 +1,4 @@
+
+
+ 女牛仔式-透视
+
\ No newline at end of file
diff --git a/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Inside.xml b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Inside.xml
new file mode 100644
index 0000000..5904ef1
--- /dev/null
+++ b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Inside.xml
@@ -0,0 +1,4 @@
+
+
+ 体内透视
+
\ No newline at end of file
diff --git a/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Penis.xml b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Penis.xml
new file mode 100644
index 0000000..dfd781e
--- /dev/null
+++ b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Penis.xml
@@ -0,0 +1,4 @@
+
+
+ 阴茎透视
+
\ No newline at end of file
diff --git a/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Race.xml b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Race.xml
new file mode 100644
index 0000000..f6253ee
--- /dev/null
+++ b/Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Race.xml
@@ -0,0 +1,5 @@
+
+
+ 阴道透视
+ 阴道透视
+
\ No newline at end of file
diff --git a/Languages/ChineseSimplified/Keyed/RJWAnimations-LanguageData.xml b/Languages/ChineseSimplified/Keyed/RJWAnimations-LanguageData.xml
index 1d72019..3addfa5 100644
--- a/Languages/ChineseSimplified/Keyed/RJWAnimations-LanguageData.xml
+++ b/Languages/ChineseSimplified/Keyed/RJWAnimations-LanguageData.xml
@@ -1,28 +1,25 @@
-
+
-
-
- RJW Animation Settings
- 启用声音覆盖
- 控制生殖器旋转
- 启用高潮颤抖
- 启用强奸颤动
- 性爱时启用心形
- 播放非性行为的动画 (牵手、亲热)
- 启用动画管理器选项卡
- 颤抖/颤动强度 (默认为 2):
- 在动画期间播放语音
- 如果没有找到外星种族的声音,则默认播放人类的声音
- 调试模式
- 调整渲染树偏移表单的浮动范围:
-
-
-
- Animation Manager
- 警告--一般情况下,你不想更改人类偏移值,只想更改外星种族偏移值或动物偏移值
- 将偏移值复制到剪贴板
- 将偏移值粘贴到 OffsetDef 中,或在 Discord 中共享
-
-
-
+ RJW 动画设置
+ 启用声音覆盖
+ 控制生殖器旋转
+ 启用高潮颤抖
+ 启用强奸颤动
+ 性爱时启用心形
+ 播放非性行为的动画 (牵手、亲热)
+ 启用动画管理器选项卡
+ 颤抖/颤动强度 (默认为 2):
+ 在动画期间播放语音
+ 如果没有找到外星种族的声音,则默认播放人类的声音
+ 调试模式
+ 调整渲染树偏移表单的浮动范围:
+ 为男性角色播放肛交动画
+ 在动画期间播放声音
+ 语音音量:
+ 声音音量:
+ 动画管理器
+ 警告--一般情况下,你不想更改人类偏移值,只想更改外星种族偏移值或动物偏移值
+ 将偏移值复制到剪贴板
+ 将偏移值粘贴到 OffsetDef 中,或在 Discord 中共享
+ 重置
\ No newline at end of file
diff --git a/LoadFolders.xml b/LoadFolders.xml
index b369dab..e41f229 100644
--- a/LoadFolders.xml
+++ b/LoadFolders.xml
@@ -35,8 +35,6 @@
/
1.6
- Patch_HumanoidAlienRaces/1.5
- Patch_SexToysMasturbation/1.5
Patch_NoRimNudeWorld/1.5
Patch_NoRimNudeWorldZoo/1.5