tweet face mail
 using System;
 using System.Linq;
 using UnityEngine;
 using Panzerwehr.Physics.Walker;
 using Panzerwehr.Physics;
 namespace Panzerwehr.Game.WalkCalculators {
     public class DebugParametricJaegerIVWalkCalculator : MonoBehaviour, IPhysicsWalkerVarProvider {
         #pragma warning disable 0649
         [SerializeField] [Range(0f, 2f)] float velocityDrag;
         [SerializeField] [Range(0f, 2f)] float torsionDrag;
         [Header("Suspension Behaviour")]
         [SerializeField] [Range(1f, 5f)] float maxSuspensionLift;
         [SerializeField] [Range(0.1f, 10f)] float legAxisAdjust;
         [SerializeField] [Range(0.1f, 10f)] float legPlaneHeightAdjust;
         [Header("Leg span and equilibrium")]
         [SerializeField] float legSpanRadius;
         [SerializeField] [Range(0.3f, 1f)] float legSpanEllipse;
         [SerializeField] [Range(0f, 10f)] float fullLegSpanAtThisVelocity;
         [SerializeField] Vector2 jointEquilibriumPoint;
         [Header("Foot collision")]
         [SerializeField] LayerMask legCollisionLayers;
         [SerializeField] [Range(0f, 1f)] float contactKeepDistance;
         [Header("Adaptive equilibrium")]
         [SerializeField] [Range(0f, 0.5f)] float limpEquilibriumOffset; // return different equilibrium
         [SerializeField] [Range(0.6f, 1f)] float limpRadiusMultiplier;
         [SerializeField] AnimationCurve dynamicEquilibriumX;
         [SerializeField] Vector2 affectEquilibriumMovingForward;
         [Header("Leg parameters")]
         [SerializeField] float individualLegTorsionTolerance;
         [SerializeField] float averageLegTorsionTolerance;
         #pragma warning restore 0649
         void Awake() {
         public void AttachTo(PhysicsWalker walker) {
             if (walker == null) return;
             Walker = walker;
             Walker.Vars = this;
         public PhysicsWalker Walker { get; set; }
         public float LegSpanRadius => legSpanRadius * ((Walker.CountRetractedLegs == 1) ? limpRadiusMultiplier : 1f);
         public LayerMask LegCollisionLayers => legCollisionLayers;
         public float VelocityDrag => velocityDrag;
         public float TorsionDrag => torsionDrag;
         public float MaxSuspensionLift => maxSuspensionLift;
         public float LegAxisAdjust => legAxisAdjust;
         public float LegPlaneHeightAdjust => legPlaneHeightAdjust;
         public float LegSpanEllipse => legSpanEllipse;
         public float ContactKeepThreshold => contactKeepDistance;
         public float FullLegSpanAtThisVelocity => fullLegSpanAtThisVelocity;
         public float IndividualLegTorsionTolerance => individualLegTorsionTolerance;
         public float AverageLegTorsionTolerance => averageLegTorsionTolerance;
         public Vector2 GetEquilibrium(LegIdentities legID) {
             if (Walker.CountRetractedLegs == 1) return GetLimpingEquilibrium(legID);
             var eq = GetNominalEquilibrium(legID);
             var longitudinaliness = RelativeVelocity.normalized.y.Map(0.8f, 1f, 0f, 1f, true);
             longitudinaliness *= Mathf.Clamp01(RelativeVelocity.magnitude * 0.2f);
             if (longitudinaliness > float.Epsilon) {
                 eq.x += Mathf.Sign(eq.x) * affectEquilibriumMovingForward.x * longitudinaliness * (1f - AwayFromOptimumHeightFactor);
                 eq.y += Mathf.Sign(eq.y) * affectEquilibriumMovingForward.y * longitudinaliness * (1f - AwayFromOptimumHeightFactor);
             eq.x += Mathf.Sign(eq.x) * dynamicEquilibriumX.Evaluate(Walker.DesiredStand);
             return eq;
         const float OptimalStand = 2f;
         public float Strain => AboveOptimumHeightFactor * 1.5f + RelativeVelocity.magnitude * 0.14f;
         public float AwayFromOptimumHeightFactor => Mathf.Clamp01(Mathf.Abs(Walker.DesiredStand - OptimalStand) * 0.6f);
         public float AboveOptimumHeightFactor => Mathf.Clamp01(Walker.DesiredStand - OptimalStand) * 0.6f;
         Vector2 RelativeVelocity => Vector3.Scale(Walker.SuspendedRigidbody.transform.InverseTransformVector(Walker.CurrentWalkVelocity), new Vector3(1f, 0f, 1f)).Flatten();
         private Vector2 GetNominalEquilibrium(LegIdentities id) {
             return Vector2.Scale(jointEquilibriumPoint, id.DirectionMultiplier());
         private Vector2 GetLimpingEquilibrium(LegIdentities legID) {
             var retracted = Walker.Legs.FirstOrDefault(leg => leg.Retracted);
             var offset = Vector2.zero;
             if (retracted != null)
                 offset = Vector2.Scale(retracted.identity.DirectionMultiplier(), GetNominalEquilibrium(legID)) * limpEquilibriumOffset;
             return GetNominalEquilibrium(legID) + offset;
 namespace Panzerwehr.API {
     public class MechBuilderAPI: IAPI {
         #region Fields and lookups
         // A general purpose cache that is persistent across a single build session and whose objects can be filled 
         // and accessed by string IDs. Use to store relevant transforms, gameobjects, floats, strings, whatever.
         Dictionary<string, object> cache = new Dictionary<string, object>();
         // Component classes are meant to be extensible - let's say you distribute a Submarine DLC and it comes with
         // a new MechComponent in a dll called CompressionTank, that you want associated with "compression_tank"
         Dictionary<string, Type> componentClassLookup = new Dictionary<string, Type>();
         // When asked to attach a script, these are the assemblies the mech builder will search in:
         List<System.Reflection.Assembly> lookupAssemblies = new List<System.Reflection.Assembly>();
         System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); 
         #region Handy accessors for current build session
         public IMech Mech { get; private set; }
         public Hangar.Model.Blueprint Blueprint { get; private set; } 
         #region Events
         public event Action<IMech> BuildFinished; 
         #region Build Session management
         void ClearBuildSession() {
             Mech = null;
         public void StartBuildSession(Hangar.Model.Blueprint blueprint) {
             this.Blueprint = blueprint;
         public void FinalizeBuildSession() {
             if (Mech == null) throw new InvalidOperationException("Mech not created; cannot finish construction");
             Debug.Log("MECH BUILT IN " + sw.Elapsed.TotalMilliseconds.ToString("0.00") + "ms");
             // ClearBuildSession();
         #region Registrations
         public void RegisterAssemblies(IEnumerable<System.Reflection.Assembly> assemblies) {
             lookupAssemblies = lookupAssemblies.Distinct().ToList();
         public void RegisterComponentClass(string typeID, Type componentClass) {
             VerifyComponentTypeDeclarationIsValid(typeID, componentClass);
             componentClassLookup.Add(typeID, componentClass);
         #region Various useful getters
         public void Cache(string id, object @object) => cache[id] = @object;
         public object Cached(string id) { cache.TryGetValue(id, out var value); return value; }
         public GameObject GetCameraObject() {
             return Camera.main.gameObject;
         public VRInput.VRConfiguration GetVRConfiguration() => GameObject.FindObjectOfType<VRInput.VRConfiguration>();
         public Transform GetVRRig() => GetVRConfiguration().TrackingRoot;
         public Type GetTypeByName(string typename) {
             return UnityUtils.FindTypeByTypeName(lookupAssemblies, typename);
         #region Object creation utilities
         public GameObject SpawnObject(string path, GameObject target = null) {
             var prefab = Resources.Load<GameObject>(path);
             if (prefab == null) throw new InvalidOperationException($"Path `{path}` could not be instantiated");
             var result = UnityEngine.Object.Instantiate(prefab);
             if (target != null) result.transform.SetParent(target.transform, false);
             return result;
         public GameObject CreateBlankObject(string name, GameObject target = null) {
             var go = new GameObject();
             go.name = name;
             if (target != null) go.transform.SetParent(target.transform, false);
             return go;
         public GameObject Wrap(string name, GameObject target) {
             if (target == null)
                 throw new InvalidOperationException($"Wrap target cannot be null");
             var parent = target.transform.parent;
             var go = new GameObject(name);
             go.transform.parent = parent;
             go.transform.localPosition = target.transform.localPosition;
             go.transform.localScale = target.transform.localScale;
             go.transform.localRotation = target.transform.localRotation;
             target.transform.parent = go.transform;
             return go;
         public GameObject FindOrCreateChild(GameObject source, string relativePath) {
             var subpaths = relativePath.Split('/');
             var currentObject = source;
             foreach (var subpath in subpaths) {
                 var child = currentObject.transform.Find(subpath);
                 currentObject = child?.gameObject ?? CreateBlankObject(subpath, currentObject);
             return currentObject;
         public void CreateMechObject(GameObject target) {
             this.Mech = target.AddComponent<Mech.Mech>();
         #region Various useful methods
         public IMechComponent InstallComponent(GameObject target, string moduleID) {
             var module = APIs.HangarAPI.Defs.GetModule(moduleID);
             if (module == null) Debug.LogError($"Null module: {moduleID}");
             var type = GetMechComponentFromModuleType(module.Type);
             if (type == null) {
                 Debug.LogWarning($"Cannot find component type for {module.Type}; will not create mech component");
                 return null;
             var component = target.AddComponent(type) as IMechComponent;
             Mech.InstallComponent(component, module);
             return component;
         public Rigidbody AttachRigidbody(GameObject @object, float mass) {
             if (@object.GetComponent<Rigidbody>() != null)
                 throw new ArgumentException($"Object {@object.name} already has a rigidbody");
             var rb = @object.AddComponent<Rigidbody>();
             rb.mass = mass;
             return rb;
         public void ReparentAtIdentity(GameObject child, GameObject parent) {
             child.transform.parent = parent.transform;
             child.transform.localPosition = Vector3.zero;
             child.transform.localRotation = Quaternion.identity;
             child.transform.localScale = Vector3.one;
         public Component AttachScript(GameObject target, string typename) {
             if (target == null) Debug.LogError($"Trying to attach {typename} script to [null]");
             var type = GetTypeByName(typename);
             if (type == null) throw new ArgumentException($"Type {typename} not found by name!");
             var mb = target.AddComponent(type) as MonoBehaviour;
             return mb;
         public void SetSlotRoot(string id, GameObject root) {
             Cache($"SLOT[{id}]", root);
         public GameObject GetSlotRoot(string id) {
             var result = Cached($"SLOT[{id}]") as GameObject;
             if (result == null) throw new InvalidOperationException($"Cannot get slot {id}");
             return result;
         public void Broadcast(string message, GameObject @object, object param = null) {
             @object.BroadcastMessage(message, param, SendMessageOptions.DontRequireReceiver);
         #region Local / private utilities
         private Type GetMechComponentFromModuleType(string typeID) {
             componentClassLookup.TryGetValue(typeID, out var type);
             return type;
         private void VerifyComponentTypeDeclarationIsValid(string typeID, Type componentClass) {
             Debug.Assert(componentClass != null, $"Trying to declare null as a component type: {typeID}");
                 $"Cannot declare mech component {componentClass.Name} as '{typeID}' since it does not implement {nameof(IMechComponent)}"
                 $"Duplicate {typeID} component type assignation: {componentClass.Name } cannot be registered!"

Panzerwehr 1949

The war should have ended in 1945. It didn't.

VR exclusive WW2 mech simulator - pending funding

It is April 1949, and the Third Battle of Stalingrad is about to begin. Infantry has long been made obsolete in the irradiated battlefield pockmarked with atomic bomb craters. In their place, a new breed of quadrupedal war machine roams the ruined streets of Stalingrad: German Jaegers and Soviet Samokhods. Under the aurora of a nuclear winter, elite pilots of both empires try to score a decisive blow...