Compare commits

...

2 Commits

Author SHA1 Message Date
c0ffee 16af5bb63a Added branching and looping animationstages for groupanimations 2024-04-16 20:44:51 -07:00
c0ffee e8fd61fb4a Rewrote anim framework to use vanilla animation system
- added extended functionality for head rotations, invisible
 - tentative design for groupanimationdefs
2024-04-16 15:10:46 -07:00
18 changed files with 691 additions and 17 deletions

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<AnimationDef>
<defName>TestAnimation1</defName>
<durationTicks>400</durationTicks>
<playWhenDowned>False</playWhenDowned>
<modExtensions>
<li Class="Rimworld_Animations.AltitudeLayer">
<PawnAltitudeLayer>Pawn</PawnAltitudeLayer>
</li>
</modExtensions>
<animationParts>
<li>
<key>Root</key>
<value>
<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
<keyframes>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>30</tick>
<angle>0</angle>
<rotation>North</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>100</tick>
<angle>0</angle>
<rotation>East</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>200</tick>
<angle>0</angle>
<rotation>South</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>300</tick>
<angle>0</angle>
<rotation>West</rotation>
<visible>true</visible>
</li>
</keyframes>
</value>
</li>
<!--
<li>
<key>Body</key>
<value>
<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
<keyframes>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>30</tick>
<angle>30</angle>
<rotation>North</rotation>
<visible>false</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>300</tick>
<angle>150</angle>
<rotation>South</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>600</tick>
<angle>30</angle>
<rotation>North</rotation>
<visible>false</visible>
</li>
</keyframes>
</value>
</li>
-->
<li>
<key>Head</key>
<value>
<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
<keyframes>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>0</tick>
<angle>0</angle>
<rotation>North</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>2</tick>
<angle>0</angle>
<rotation>East</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>4</tick>
<angle>0</angle>
<rotation>South</rotation>
<visible>true</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>6</tick>
<angle>0</angle>
<rotation>West</rotation>
<visible>false</visible>
</li>
<li Class="Rimworld_Animations.ExtendedKeyframe">
<tick>8</tick>
<angle>0</angle>
<rotation>North</rotation>
<visible>false</visible>
</li>
</keyframes>
</value>
</li>
</animationParts>
</AnimationDef>
</Defs>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<AnimationDef>
<defName>TestAnimation2</defName>
<durationTicks>200</durationTicks>
<startOnRandomTick>False</startOnRandomTick>
<playWhenDowned>False</playWhenDowned>
<animationParts>
<li>
<key>Root</key>
<value>
<workerClass>AnimationWorker_Keyframes</workerClass>
<keyframes>
<li>
<tick>0</tick>
<angle>23</angle>
</li>
<li>
<tick>6</tick>
<angle>-5</angle>
</li>
<li>
<tick>12</tick>
<angle>4</angle>
</li>
<li>
<tick>188</tick>
<angle>-1</angle>
</li>
</keyframes>
</value>
</li>
<li>
<key>Head</key>
<value>
<workerClass>AnimationWorker_Keyframes</workerClass>
<keyframes>
<li>
<tick>0</tick>
<angle>5</angle>
</li>
<li>
<tick>6</tick>
<angle>-5</angle>
</li>
<li>
<tick>12</tick>
<angle>4</angle>
</li>
<li>
<tick>128</tick>
<angle>-1</angle>
</li>
</keyframes>
</value>
</li>
</animationParts>
</AnimationDef>
</Defs>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<GroupAnimationDef>
<defName>TestGroupAnimation1</defName>
<numActors>2</numActors>
<AnimationStages>
<li Class="Rimworld_Animations.AnimationStage_TicksDuration">
<ticks>200</ticks>
<animationDefs>
<li>Pawn1_Stage1_TestAnimation1</li>
<li>Pawn2_Stage1_TestAnimation2</li>
</animationDefs>
</li>
<li Class="Rimworld_Animations.AnimationStage_LoopRandomSelectChance">
<loops>10</loops>
<animationOptions>
<li>
<probability>3</probability>
<animationDefs>
<li>Pawn1_Stage2_Variant1</li>
<li>Pawn2_Stage2_Variant1</li>
</animationDefs>
</li>
<li>
<probability>1</probability>
<animationDefs>
<li>Pawn1_Stage2_Variant2</li>
<li>Pawn2_Stage2_Variant2</li>
</animationDefs>
</li>
</animationOptions>
</li>
<li Class="Rimworld_Animations.AnimationStage_Branch">
<paths>
<li Class="Rimworld_Animations.AnimationStage_LoopRandomSelectChance">
<numberOfLoops>10</numberOfLoops>
<animationOptions>
<li>
<probability>3</probability>
<li>Pawn1_Stage2_Variant1</li>
<li>Pawn2_Stage2_Variant1</li>
</li>
<li>
<probability>1</probability>
<li>Pawn1_Stage2_Variant2</li>
<li>Pawn2_Stage2_Variant2</li>
</li>
</animationOptions>
</li>
<li Class="Rimworld_Animations.AnimationStage_LoopRandomSelectChance">
<numberOfLoops>10</numberOfLoops>
<animationOptions>
<li>
<probability>3</probability>
<li>Pawn1_Stage2_Variant1</li>
<li>Pawn2_Stage2_Variant1</li>
</li>
<li>
<probability>1</probability>
<li>Pawn1_Stage2_Variant2</li>
<li>Pawn2_Stage2_Variant2</li>
</li>
</animationOptions>
</li>
</paths>
</li>
</AnimationStages>
<contexts>
<li Class="Rimworld_Animations.Actor_Sextype">
<sexTypes>
<li>Sex</li>
</sexTypes>
<actorShift>0</actorShift>
</li>
<li Class="Rimworld_Animations.Actor_Sextype">
<sexTypes>
<li>Sex_Reverse</li>
</sexTypes>
<actorShift>1</actorShift>
</li>
<li Class="Rimworld_Animations.Actor_Custom_Races">
<sexTypes>
<li>Sex</li>
</sexTypes>
<races>
<li>Human</li>
<li>Dog</li>
</races>
<actorShift>0</actorShift>
</li>
<li Class="Rimworld_Animations.Actor_Custom_Races">
<sexTypes>
<li>Sex_Reverse</li>
</sexTypes>
<races>
<li>Dog</li>
<li>Human</li>
</races>
<actorShift>1</actorShift>
</li>
</contexts>
</GroupAnimationDef>
</Defs>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<!-- Add genitalia rendernode to pawns, body is parent
<Operation Class="PatchOperationAdd">
<xpath>Defs/PawnRenderTreeDef[defName="Humanlike"]/root/children/li[debugLabel="Body"]/children</xpath>
<value>
<li Class="PawnRenderNodeProperties_Overlay">
<debugLabel>Genitalia</debugLabel>
<workerClass>PawnRenderNodeWorker_OverlayWounds</workerClass>
<overlayLayer>Body</overlayLayer>
<baseLayer>3</baseLayer>
</li>
</value>
</Operation>
-->
</Patch>

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations
{
class AnimationWorker_KeyframesExtended : AnimationWorker_Keyframes
{
public AnimationWorker_KeyframesExtended(AnimationDef def, Pawn pawn, AnimationPart part, PawnRenderNode node) : base(def, pawn, part, node)
{
}
//use extendedkeyframes to determine addon facing
public Rot4 facingAtTick(int tick)
{
//if ticks are < first keyframe tick, just be stuck to first keyframe rot
if (tick <= this.part.keyframes[0].tick) {
return (this.part.keyframes[0] as ExtendedKeyframe).rotation;
}
//if ticks are > last keyframe tick, just be stuck to last keyframe rot
if (tick >= this.part.keyframes[this.part.keyframes.Count - 1].tick)
{
return (this.part.keyframes[this.part.keyframes.Count - 1] as ExtendedKeyframe).rotation;
}
Verse.Keyframe keyframe = this.part.keyframes[0];
Verse.Keyframe keyframe2 = this.part.keyframes[this.part.keyframes.Count - 1];
int i = 0;
while (i < this.part.keyframes.Count)
{
if (tick <= this.part.keyframes[i].tick)
{
keyframe2 = this.part.keyframes[i];
if (i > 0)
{
keyframe = this.part.keyframes[i - 1];
break;
}
break;
}
else
{
i++;
}
}
return (keyframe as ExtendedKeyframe).rotation;
}
public bool visibleAtTick(int tick)
{
//if ticks are < first keyframe tick, just be stuck to first keyframe rot
if (tick <= this.part.keyframes[0].tick)
{
return (this.part.keyframes[0] as ExtendedKeyframe).visible;
}
//if ticks are > last keyframe tick, just be stuck to last keyframe rot
if (tick >= this.part.keyframes[this.part.keyframes.Count - 1].tick)
{
return (this.part.keyframes[this.part.keyframes.Count - 1] as ExtendedKeyframe).visible;
}
Verse.Keyframe keyframe = this.part.keyframes[0];
Verse.Keyframe keyframe2 = this.part.keyframes[this.part.keyframes.Count - 1];
int i = 0;
while (i < this.part.keyframes.Count)
{
if (tick <= this.part.keyframes[i].tick)
{
keyframe2 = this.part.keyframes[i];
if (i > 0)
{
keyframe = this.part.keyframes[i - 1];
break;
}
break;
}
else
{
i++;
}
}
return (keyframe as ExtendedKeyframe).visible;
}
}
}

View File

@ -0,0 +1,22 @@
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
[DefOf]
public static class AnimationDefOf
{
static AnimationDefOf()
{
DefOfHelper.EnsureInitializedInCtor(typeof(SoundDefOf));
}
public static AnimationDef TestAnimation1;
public static AnimationDef TestAnimation2;
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
public abstract class AnimationStage
{
//Return a list containing a tuple; int for how long the animation should play for
public abstract List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed);
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
public class AnimationStage_Branch : AnimationStage
{
List<AnimationStage> paths;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
{
return paths[(seed * 59) % paths.Count].GetAnimations(actor, seed);
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
public class AnimationStage_LoopRandomSelectChance : AnimationStage
{
public int loops;
public List<AnimationLoopOption> animationOptions;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
{
int numberOfActors = animationOptions[0].animationDefs.Count;
List<Tuple<int, AnimationDef>> animations = new List<Tuple<int, AnimationDef>>();
for (int i = 0; i < loops; i++)
{
AnimationLoopOption option = getAnimationLoopOptionByWeight(seed + i, out int longestAnimLength);
Tuple<int, AnimationDef> animation = Tuple.Create(longestAnimLength, option.animationDefs[actor]);
animations.Append(animation);
}
return animations;
}
public class AnimationLoopOption
{
public int probability;
public List<AnimationDef> animationDefs;
}
//select random element from loop options by weight; also calculate the longest anim length
public AnimationLoopOption getAnimationLoopOptionByWeight(int seed, out int longestAnimLength)
{
int totalWeight = animationOptions.Sum(x => x.probability);
int randomNumber = (seed * 56) % totalWeight;
int cumulativeWeight = 0;
foreach(AnimationLoopOption option in animationOptions) {
cumulativeWeight += option.probability;
if (randomNumber <= cumulativeWeight)
{
longestAnimLength = option.animationDefs.Max(x => x.durationTicks);
return option;
}
}
longestAnimLength = animationOptions[0].animationDefs.Max(x => x.durationTicks);
return animationOptions[0];
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
public class AnimationStage_TicksDuration : AnimationStage
{
int ticks;
List<AnimationDef> animationDefs;
public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
{
return new List<Tuple<int, AnimationDef>>() { Tuple.Create(ticks, animationDefs[actor]) };
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations
{
public class ExtendedKeyframe : Verse.Keyframe
{
public Rot4 rotation;
public SoundDef sound = null;
public bool visible;
}
}

View File

@ -0,0 +1,24 @@
using HarmonyLib;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
/*
[HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
public class HarmonyPatch_JobDriver_Sex
{
public static void Prefix(JobDriver_Sex __instance)
{
Pawn partner = __instance.Partner;
}
}
*/
}

View File

@ -16,7 +16,9 @@ namespace Rimworld_Animations {
don't play anim for now
*/
if(__instance is JobDriver_Masturbate || __instance is JobDriver_ViolateCorpse) {
return;
}
if(!AnimationSettings.PlayAnimForNonsexualActs && NonSexualAct(__instance))
@ -25,10 +27,10 @@ namespace Rimworld_Animations {
}
Pawn pawn = __instance.pawn;
Pawn partner = __instance.Target as Pawn;
Building_Bed bed = __instance.Bed;
if ((__instance.Target as Pawn)?.jobs?.curDriver is JobDriver_SexBaseReciever) {
if (partner?.jobs?.curDriver is JobDriver_SexBaseReciever partnerSexBaseReceiver) {
Pawn Target = __instance.Target as Pawn;
@ -37,17 +39,12 @@ namespace Rimworld_Animations {
int preAnimDuration = __instance.duration;
int AnimationTimeTicks = 0;
if (bed != null) {
RerollAnimations(bed as Thing);
}
else {
RerollAnimations();
}
List<Pawn> participants = partnerSexBaseReceiver.parteners.Append(partner).ToList();
RerollAnimations(participants);
//Modify Orgasm ticks to only orgasm as many times as RJW stock orgasm allows
if(AnimationTimeTicks != 0)
if (AnimationTimeTicks != 0)
{
__instance.orgasmstick = preAnimDuration * __instance.orgasmstick / AnimationTimeTicks;
}
@ -56,9 +53,11 @@ namespace Rimworld_Animations {
}
}
public static void RerollAnimations(Thing bed = null) {
public static void RerollAnimations(List<Pawn> participants) {
participants[0].Drawer.renderer.SetAnimation(AnimationDefOf.TestAnimation1);
participants[1].Drawer.renderer.SetAnimation(AnimationDefOf.TestAnimation2);
}

View File

@ -0,0 +1,47 @@
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace Rimworld_Animations
{
//Head Rotation Code
[HarmonyPatch(typeof(PawnRenderNode), "AppendRequests")]
public static class HarmonyPatch_PawnRenderNode
{
//if rendernodetag is head, update PawnDrawParms so that head, and all children, are rotated for anim
public static bool Prefix(ref PawnRenderNode __instance, ref PawnDrawParms parms)
{
if (__instance.AnimationWorker is AnimationWorker_KeyframesExtended extendedAnimWorker)
{
//INVIS IF ANIM CALLS FOR IT
//replace maybe?
if (!extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick))
{
__instance.requestRecache = true;
return false;
}
// HEAD ROTATION ADJUST FACING
Rot4 animFacing = extendedAnimWorker.facingAtTick(__instance.tree.AnimationTick);
if (parms.facing != animFacing)
{
//requestRecache or else it won't update properly
__instance.requestRecache = true;
parms.facing = animFacing;
}
}
return true;
}
}
}

View File

@ -0,0 +1,27 @@
using HarmonyLib;
using rjw;
using UnityEngine;
using Verse;
namespace Rimworld_Animations {
[HarmonyPatch(typeof(Pawn_DrawTracker), "DrawPos", MethodType.Getter)]
public static class HarmonyPatch_Pawn_DrawTracker {
public static bool Prefix(ref Pawn ___pawn, ref Vector3 __result) {
if (___pawn?.Drawer?.renderer?.renderTree?.rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended
&& ___pawn.jobs?.curDriver is JobDriver_SexBaseInitiator sexdriver)
{
//align pos on top of partner
__result = sexdriver.Partner.Drawer.DrawPos;
//change height so that they're all layered properly
if (sexdriver.Partner.jobs.curDriver is JobDriver_SexBaseReciever)
for (int i = 0; i < )
return false;
}
return true;
}
}
}

View File

@ -72,17 +72,27 @@
<Compile Include="1.5\Source\Actors\Actor.cs" />
<Compile Include="1.5\Source\Actors\AlienRaceOffset.cs" />
<Compile Include="1.5\Source\Actors\BodyTypeOffset.cs" />
<Compile Include="1.5\Source\AnimationWorkers\AnimationWorker_KeyframesExtended.cs" />
<Compile Include="1.5\Source\Comps\CompProperties_ThingAnimator.cs" />
<Compile Include="1.5\Source\Comps\CompThingAnimator.cs" />
<Compile Include="1.5\Source\Defs\AnimationDefOf.cs" />
<Compile Include="1.5\Source\GroupAnimationStages\AnimationStage.cs" />
<Compile Include="1.5\Source\GroupAnimationStages\AnimationStage_Branch.cs" />
<Compile Include="1.5\Source\GroupAnimationStages\AnimationStage_LoopRandomSelectChance.cs" />
<Compile Include="1.5\Source\GroupAnimationStages\AnimationStage_TicksDuration.cs" />
<Compile Include="1.5\Source\Keyframes\ExtendedKeyframe.cs" />
<Compile Include="1.5\Source\MainTabWindows\MainTabWindow_OffsetConfigure.cs" />
<Compile Include="1.5\Source\MainTabWindows\OffsetMainButtonDefOf.cs" />
<Compile Include="1.5\Source\MainTabWindows\WorldComponent_UpdateMainTab.cs" />
<Compile Include="1.5\Source\Patches\Harmony_PatchAll.cs" />
<Compile Include="1.5\Source\Patches\RimworldPatches\HarmonyPatch_PawnRenderNode.cs" />
<Compile Include="1.5\Source\Patches\RimworldPatches\HarmonyPatch_Pawn_DrawTracker.cs" />
<Compile Include="1.5\Source\Patches\RimworldPatches\HarmonyPatch_Thing.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\HarmonyPatch_PlaySexSounds.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\HarmonyPatch_SexTick.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\HarmonyPatch_WorkGiverSex.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_JoinInBed.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_Sex.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseInitiator.cs" />
<Compile Include="1.5\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseReceiverLoved.cs" />
<Compile Include="1.5\Source\Settings\AnimationSettings.cs" />
@ -90,10 +100,16 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="1.1\Assemblies\Rimworld-Animations.dll" />
<Content Include="1.5\Assemblies\0MultiplayerAPI.dll" />
<Content Include="1.5\Assemblies\Rimworld-Animations.dll" />
<Content Include="1.5\Assemblies\RJW.dll" />
<Content Include="1.5\Defs\AnimationDefs\TestAnimation1.xml" />
<Content Include="1.5\Defs\AnimationDefs\TestAnimation2.xml" />
<Content Include="1.5\Defs\GroupAnimationDefs\TestGroupAnimation1.xml" />
<Content Include="1.5\Defs\MainTabDefs\MainButtonDef.xml" />
<Content Include="1.5\Defs\SoundDefs\Sounds_Sex.xml" />
<Content Include="1.5\Patches\CompatibilityPatch_FacialAnimation.xml" />
<Content Include="1.5\Patches\Patch_GenitaliaRenderNode.xml" />
<Content Include="1.5\Sounds\Sex\Clap_1.wav" />
<Content Include="1.5\Sounds\Sex\Clap_2.wav" />
<Content Include="1.5\Sounds\Sex\Clap_3.wav" />
@ -133,10 +149,7 @@
<Content Include="LoadFolders.xml" />
</ItemGroup>
<ItemGroup>
<Folder Include="1.2\" />
<Folder Include="1.5\Assemblies\" />
<Folder Include="1.5\Defs\AnimationDefs\" />
<Folder Include="1.5\Source\Defs\" />
<Folder Include="1.5\Defs\SexAnimationDefs\" />
<Folder Include="1.5\Source\Extensions\" />
<Folder Include="1.5\Source\Patches\OtherModPatches\" />
</ItemGroup>