From aacf38c8a269b4f98d171199378a035e1a10c3c2 Mon Sep 17 00:00:00 2001 From: LVKavidya Date: Thu, 2 Jul 2026 12:52:15 +0800 Subject: [PATCH 1/3] Update Simplified Chinese translation --- .../MainButtonDef/MainButtonDef.xml | 5 ++ .../AnimationPropDef_Cowgirl_Xray.xml | 4 ++ .../AnimationPropDef_Xray_Inside.xml | 4 ++ .../AnimationPropDef_Xray_Penis.xml | 4 ++ .../AnimationPropDef_Xray_Race.xml | 5 ++ .../Keyed/RJWAnimations-LanguageData.xml | 49 +++++++++---------- 6 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 Languages/ChineseSimplified/DefInjected/MainButtonDef/MainButtonDef.xml create mode 100644 Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Cowgirl_Xray.xml create mode 100644 Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Inside.xml create mode 100644 Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Penis.xml create mode 100644 Languages/ChineseSimplified/DefInjected/Rimworld_Animations.AnimationPropDef/AnimationPropDef_Xray_Race.xml 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 From 21ef8d24e0b97cf025b6d8843d9546271f412862 Mon Sep 17 00:00:00 2001 From: Rob The Robot Guard <55799-RobTheRobotGuard@users.noreply.gitgud.io> Date: Wed, 7 Oct 2026 17:01:46 +0100 Subject: [PATCH 2/3] Fix necrophilia animations and issues from code review Necrophilia: - Anchor the living pawn to the corpse instead of its inner pawn, whose position is stale once the corpse is hauled - Corpses only TickRare, so a living participant's animator now ticks dead actors' animators; multi-stage animations no longer desync - Save driven corpses with saveDestroyedThings (dead pawns count as destroyed and would otherwise load as null) - Reapply the renderer animation mid-way after loading, since vanilla doesn't save it Review fixes: - Priority-0 fallback contexts can now be selected; negative priorities no longer log "Negative weight" errors; drop no-op OrderBy - Remove dead HAR and SexToys entries from the v1.6 LoadFolders block - Neverending sex: retry about once a second instead of every tick, guard null participants, reset RJW timers, handle corpses - Ignore anchors that were destroyed or despawned - Use pivot (not scale) for the inverse pivot translation in TryGetMatrix - Offset tab Reset removes the saved override and restores def offsets - Merge root/child sound handling; respect playSounds for the root node; read child voices from the child node; no voices from corpses - Force-save settings whose load defaults differ from field defaults - Debug-only interaction logging; null checks for partner, offsetDefs, whitelist and short blacklists - Remove empty or no-op Harmony patch classes Co-Authored-By: Claude Opus 5.5 --- .../GroupAnimationContext.cs | 4 +- .../GroupAnimations/GroupAnimationDef.cs | 4 +- 1.6/Source/Comps/CompExtendedAnimator.cs | 240 ++++++++++-------- .../BaseExtendedAnimatorAnchor.cs | 3 + .../ExtendedAnimatorAnchor_Thing.cs | 3 + .../MainTabWindow_OffsetConfigure.cs | 21 +- .../HarmonyPatch_JobDriver_Masturbate.cs | 55 ---- .../JobDrivers/HarmonyPatch_JobDriver_Sex.cs | 75 ++++-- ...HarmonyPatch_JobDriver_SexBaseInitiator.cs | 17 +- ...onyPatch_JobDriver_SexBaseReceiverRaped.cs | 29 --- .../HarmonyPatch_PawnRenderTree.cs | 4 +- .../RimworldPatches/HarmonyPatch_Thing.cs | 33 --- 1.6/Source/Settings/PawnScaledOffsets.cs | 4 +- 1.6/Source/Settings/RJWAnimationSettings.cs | 14 +- 1.6/Source/Utilities/AnimationUtility.cs | 44 +++- LoadFolders.xml | 2 - 16 files changed, 273 insertions(+), 279 deletions(-) delete mode 100644 1.6/Source/Patches/RJWPatches/JobDrivers/HarmonyPatch_JobDriver_Masturbate.cs delete mode 100644 1.6/Source/Patches/RJWPatches/JobDrivers/SexBaseReceivers/HarmonyPatch_JobDriver_SexBaseReceiverRaped.cs delete mode 100644 1.6/Source/Patches/RimworldPatches/HarmonyPatch_Thing.cs 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/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
  • From 56867bab699ad19d36e593e95279b3d9272424ed Mon Sep 17 00:00:00 2001 From: c0ffeeeeeeee Date: Sun, 11 Oct 2026 21:03:28 +0000 Subject: [PATCH 3/3] updated to version 2.2.1 fixes to necro anims, voices, anim loading, lots of cool stuff by Rob The Robot Guard Simplified Chinese by LVKavidya --- About/About.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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