mirror of
https://gitgud.io/c0ffeeeeeeee/rimworld-animations.git
synced 2026-10-22 15:04:28 +00:00
Compare commits
6 commits
e9e0d57ed1
...
2af60a8c33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af60a8c33 | ||
|
|
56867bab69 | ||
|
|
52feae9ba6 | ||
|
|
8c9df3c0da | ||
|
|
21ef8d24e0 | ||
|
|
aacf38c8a2 |
23 changed files with 310 additions and 297 deletions
|
|
@ -91,7 +91,7 @@ namespace Rimworld_Animations
|
|||
|
||||
public List<Pawn> FindAnyWorkingSet(List<Pawn> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Pawn> 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<CompExtendedAnimator>();
|
||||
|
||||
//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<Pawn>();
|
||||
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<Pawn> pawnsToStop = drivenPawns;
|
||||
drivenPawns = null;
|
||||
foreach (Pawn drivenPawn in pawnsToStop)
|
||||
{
|
||||
drivenPawn?.TryGetComp<CompExtendedAnimator>()?.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<AnimationDef>(ref animationQueue, "animations_queue");
|
||||
Scribe_Defs.Look<GroupAnimationDef>(ref currentGroupAnimation, "animations_groupAnimationDef");
|
||||
Scribe_Deep.Look<BaseExtendedAnimatorAnchor>(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<Pawn>(ref this.drivenPawns, "animations_drivenPawns", true, LookMode.Reference);
|
||||
Scribe_Values.Look<Vector3>(ref this.offset, "animations_offset", Vector3.zero);
|
||||
Scribe_Values.Look<int>(ref this.rotation, "animations_rotationOffset", 0);
|
||||
Scribe_Defs.Look<VoiceDef>(ref this.voice, "animations_voice");
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<Toil> __result)
|
||||
{
|
||||
|
||||
var toils = __result.ToList();
|
||||
|
||||
//sex toil
|
||||
toils[1].initAction += delegate ()
|
||||
{
|
||||
CompExtendedAnimator pawnAnimator = __instance.pawn.TryGetComp<CompExtendedAnimator>();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CompExtendedAnimator>().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<CompExtendedAnimator>() is CompExtendedAnimator animator)
|
||||
|| animator.IsAnimating)
|
||||
{
|
||||
|
||||
//start a new animation for all the pawns paired with receiver job driver
|
||||
List<Pawn> 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> { pawn };
|
||||
}
|
||||
|
||||
|
||||
GroupAnimationDef animation = AnimationUtility.FindGroupAnimation(participants, out List<Pawn> 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<Pawn> 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> { pawn, corpse.InnerPawn };
|
||||
anchor = anchor ?? corpse;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = new List<Pawn> { pawn };
|
||||
anchor = anchor ?? pawn;
|
||||
}
|
||||
|
||||
GroupAnimationDef animation = AnimationUtility.FindGroupAnimation(participants, out List<Pawn> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ namespace Rimworld_Animations
|
|||
{
|
||||
participants = new List<Pawn>() { pawn };
|
||||
}
|
||||
else if (partner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (__instance is JobDriver_ViolateCorpse)
|
||||
{
|
||||
participants = new List<Pawn>() { 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
|
||||
|
|
|
|||
|
|
@ -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<Toil> __result)
|
||||
{
|
||||
//added for sudden end of jobdriver
|
||||
__result.Last().AddFinishAction(delegate {
|
||||
AnimationUtility.StopGroupAnimation(__instance.pawn);
|
||||
});
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<CompThingAnimator>();
|
||||
if (thingAnimator != null && thingAnimator.isAnimating)
|
||||
{
|
||||
thingAnimator.AnimateThing(__instance);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CompExtendedAnimator>().PlayGroupAnimation(groupAnimationDef, null, i, seed, participants);
|
||||
|
|
@ -38,6 +40,27 @@ namespace Rimworld_Animations {
|
|||
participants[i].TryGetComp<CompExtendedAnimator>().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<Pawn> participants)
|
||||
{
|
||||
Pawn driver = participants.FirstOrDefault(x => !x.Dead);
|
||||
if (driver == null) return;
|
||||
|
||||
CompExtendedAnimator driverAnimator = driver.TryGetComp<CompExtendedAnimator>();
|
||||
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<CompExtendedAnimator>().PlayGroupAnimation(groupAnimationDef, i, seed, participants);
|
||||
}
|
||||
|
||||
DriveDeadParticipants(participants);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -67,8 +92,9 @@ namespace Rimworld_Animations {
|
|||
|
||||
public static GroupAnimationDef FindGroupAnimation(List<Pawn> participants, out List<Pawn> pawnOrder)
|
||||
{
|
||||
pawnOrder = null;
|
||||
if (participants.NullOrEmpty() || participants.Contains(null)) return null;
|
||||
|
||||
|
||||
/* This method is fooken expensive
|
||||
List<(GroupAnimationDef, List<Pawn>, int Priority)> potentialAnimations = new List<(GroupAnimationDef, List<Pawn>, int)>();
|
||||
|
||||
|
|
@ -108,13 +134,17 @@ namespace Rimworld_Animations {
|
|||
}
|
||||
|
||||
|
||||
//Randomize the list,
|
||||
potentialAnimations.OrderBy(_ => Rand.Int);
|
||||
(GroupAnimationDef, List<Pawn>, int) selectedAnimation;
|
||||
|
||||
potentialAnimations.TryRandomElementByWeight(((GroupAnimationDef, List<Pawn>, int) x) => x.Item3, out selectedAnimation);
|
||||
//Find a highest priority animation, out as selectedAnimation
|
||||
// .TryMaxBy(((GroupAnimationDef, List<Pawn>, int) x) => x.Item3, out (GroupAnimationDef, List<Pawn>, 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<Pawn>, 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;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<li>1.6</li>
|
||||
</supportedVersions>
|
||||
<packageId>c0ffee.rimworld.animations</packageId>
|
||||
<modVersion>2.2.0</modVersion>
|
||||
<modVersion>2.2.1</modVersion>
|
||||
<modIconPath>UI/MainTab</modIconPath>
|
||||
<modDependencies>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
<OffsetManager.label>动画管理器</OffsetManager.label>
|
||||
<OffsetManager.description>编辑角色动画的偏移量。</OffsetManager.description>
|
||||
</LanguageData>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
<AnimProp_Cowgirl_Xray.animPropProperties.debugLabel>女牛仔式-透视</AnimProp_Cowgirl_Xray.animPropProperties.debugLabel>
|
||||
</LanguageData>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
<AnimationProp_Xray_Inside.animPropProperties.debugLabel>体内透视</AnimationProp_Xray_Inside.animPropProperties.debugLabel>
|
||||
</LanguageData>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
<AnimationProp_Penis.animPropProperties.debugLabel>阴茎透视</AnimationProp_Penis.animPropProperties.debugLabel>
|
||||
</LanguageData>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
<AnimationProp_Vagina.alternateProps.0.props.debugLabel>阴道透视</AnimationProp_Vagina.alternateProps.0.props.debugLabel>
|
||||
<AnimationProp_Vagina.animPropProperties.debugLabel>阴道透视</AnimationProp_Vagina.animPropProperties.debugLabel>
|
||||
</LanguageData>
|
||||
|
|
@ -1,28 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LanguageData>
|
||||
|
||||
<!-- Mod menu -->
|
||||
<RimAnim_ModSettings>RJW Animation Settings</RimAnim_ModSettings>
|
||||
<RimAnim_SoundOverride>启用声音覆盖</RimAnim_SoundOverride>
|
||||
<RimAnim_GenitalRotation>控制生殖器旋转</RimAnim_GenitalRotation>
|
||||
<RimAnim_OrgasmQuiver>启用高潮颤抖</RimAnim_OrgasmQuiver>
|
||||
<RimAnim_RapeShiver>启用强奸颤动</RimAnim_RapeShiver>
|
||||
<RimAnim_HeartsDuringLovin>性爱时启用心形</RimAnim_HeartsDuringLovin>
|
||||
<RimAnim_PlayNonsexual>播放非性行为的动画 (牵手、亲热)</RimAnim_PlayNonsexual>
|
||||
<RimAnim_AnimManagerTab>启用动画管理器选项卡</RimAnim_AnimManagerTab>
|
||||
<RimAnim_ShiverIntensity>颤抖/颤动强度 (默认为 2): </RimAnim_ShiverIntensity>
|
||||
<RimAnim_Voices>在动画期间播放语音</RimAnim_Voices>
|
||||
<RimAnim_HumanlikeVoicesDefault>如果没有找到外星种族的声音,则默认播放人类的声音</RimAnim_HumanlikeVoicesDefault>
|
||||
<RimAnim_DebugMode>调试模式</RimAnim_DebugMode>
|
||||
<RimAnim_FloatRangeRenderTree>调整渲染树偏移表单的浮动范围:</RimAnim_FloatRangeRenderTree>
|
||||
|
||||
|
||||
<!-- Main Tab Window -->
|
||||
<RimAnims_AnimManager>Animation Manager</RimAnims_AnimManager>
|
||||
<RimAnims_Warning>警告--一般情况下,你不想更改人类偏移值,只想更改外星种族偏移值或动物偏移值</RimAnims_Warning>
|
||||
<RimAnims_CopyToClipboard>将偏移值复制到剪贴板</RimAnims_CopyToClipboard>
|
||||
<RimAnims_ShareSettings>将偏移值粘贴到 OffsetDef 中,或在 Discord 中共享</RimAnims_ShareSettings>
|
||||
|
||||
|
||||
|
||||
<RimAnim_ModSettings>RJW 动画设置</RimAnim_ModSettings>
|
||||
<RimAnim_SoundOverride>启用声音覆盖</RimAnim_SoundOverride>
|
||||
<RimAnim_GenitalRotation>控制生殖器旋转</RimAnim_GenitalRotation>
|
||||
<RimAnim_OrgasmQuiver>启用高潮颤抖</RimAnim_OrgasmQuiver>
|
||||
<RimAnim_RapeShiver>启用强奸颤动</RimAnim_RapeShiver>
|
||||
<RimAnim_HeartsDuringLovin>性爱时启用心形</RimAnim_HeartsDuringLovin>
|
||||
<RimAnim_PlayNonsexual>播放非性行为的动画 (牵手、亲热)</RimAnim_PlayNonsexual>
|
||||
<RimAnim_AnimManagerTab>启用动画管理器选项卡</RimAnim_AnimManagerTab>
|
||||
<RimAnim_ShiverIntensity>颤抖/颤动强度 (默认为 2): </RimAnim_ShiverIntensity>
|
||||
<RimAnim_Voices>在动画期间播放语音</RimAnim_Voices>
|
||||
<RimAnim_HumanlikeVoicesDefault>如果没有找到外星种族的声音,则默认播放人类的声音</RimAnim_HumanlikeVoicesDefault>
|
||||
<RimAnim_DebugMode>调试模式</RimAnim_DebugMode>
|
||||
<RimAnim_FloatRangeRenderTree>调整渲染树偏移表单的浮动范围:</RimAnim_FloatRangeRenderTree>
|
||||
<RimAnim_MaleAnalCanBeFucked>为男性角色播放肛交动画</RimAnim_MaleAnalCanBeFucked>
|
||||
<RimAnim_PlaySounds>在动画期间播放声音</RimAnim_PlaySounds>
|
||||
<RimAnim_VoicesVolume>语音音量:</RimAnim_VoicesVolume>
|
||||
<RimAnim_SoundsVolume>声音音量:</RimAnim_SoundsVolume>
|
||||
<RimAnims_AnimManager>动画管理器</RimAnims_AnimManager>
|
||||
<RimAnims_Warning>警告--一般情况下,你不想更改人类偏移值,只想更改外星种族偏移值或动物偏移值</RimAnims_Warning>
|
||||
<RimAnims_CopyToClipboard>将偏移值复制到剪贴板</RimAnims_CopyToClipboard>
|
||||
<RimAnims_ShareSettings>将偏移值粘贴到 OffsetDef 中,或在 Discord 中共享</RimAnims_ShareSettings>
|
||||
<RimAnims_Reset>重置</RimAnims_Reset>
|
||||
</LanguageData>
|
||||
|
|
@ -35,8 +35,6 @@
|
|||
<v1.6>
|
||||
<li>/</li>
|
||||
<li>1.6</li>
|
||||
<li IfModActive="erdelf.HumanoidAlienRaces">Patch_HumanoidAlienRaces/1.5</li>
|
||||
<li IfModActive="c0ffee.SexToysMasturbation">Patch_SexToysMasturbation/1.5</li>
|
||||
<li IfModNotActive="shauaputa.rimnudeworld">Patch_NoRimNudeWorld/1.5</li>
|
||||
<li IfModNotActive="shauaputa.rimnudeworldzoo">Patch_NoRimNudeWorldZoo/1.5</li>
|
||||
</v1.6>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue