Vehicles/VehicleScripts/RopeWinch.lua

RopeWinch implements all features required by snowcats that are equipped with winch devices (such as the PistenBully 600 W). These vehicles can then be attached to all anchor points (of type AnchorPoint). Also, there is a VehicleAnchor script which allows for having winch anchor points mounted to vehicles.

Similar to CylinderAnimator, most performance-relevant operations are done by C# code. All RopeWinch functions that are called, but not documented here, are written in C# (e.g. RopeWinch.new or RopeWinch.setIntakeRotation).

  26  RopeWinch                           = RopeWinch or {};
  27  RopeWinch.anchorPoints              = RopeWinch.anchorPoints or {};

RopeWinch.registerAnchorPoint(name, obj)

Register an AnchorPoint so that it can be used by RopeWinch. Note that anchor points are always indexed by their name (a string) as passed to this function!

  32  function RopeWinch.registerAnchorPoint(name, obj)
  33      RopeWinch.anchorPoints[name or ""]  = obj;
  34  end;

RopeWinch.unregisterAnchorPoint(name)

Unregister an AnchorPoint

  38  function RopeWinch.unregisterAnchorPoint(name)
  39      RopeWinch.anchorPoints[name or ""]  = nil;
  40  end;

RopeWinch:load(dataTable)

We need to load some data from our configuration table (dataTable).

First, we set up all custom functions that this script requires. Note that VehicleManager:newFunction() does not allow for return values, therefore we have to link some functions directly. Then, we read in configuration data and apply some defaults.

  47  function RopeWinch:load(dataTable)
  48      self.attachWinchToAnchorPoint   = VehicleManager:newFunction("attachWinchToAnchorPoint");
  49      self.setIsRopeTensed            = VehicleManager:newFunction("setIsRopeTensed");
  50      self.ropeWinchConnectorCallback = VehicleManager:newFunction("ropeWinchConnectorCallback");
  51      
  52      self.getRopeWinchConnectorActive= RopeWinch.getRopeWinchConnectorActive;
  53      self.getMayRopeWinchDisconnect  = RopeWinch.getMayRopeWinchDisconnect;
  54      
  55      if dataTable.ropeWinch ~= nil then
  56          local v                     = dataTable.ropeWinch;
  57          self.winchRoot              = getChild(self.id, v.winchRoot or "");
  58          self.winchIntake            = getChild(self.id, v.winchIntake or "");
  59          local ropeDiameter          = v.ropeDiameter or 0.011;
  60          local ropeWeightPerMeter    = v.weightPerMeter or 45;
  61  
  62          self.ropeWinchNode          = RopeWinch.new(self.id, self.winchRoot, self.winchIntake, ropeDiameter, ropeWeightPerMeter);
  63          self.anchorPointName        = nil;
  64  
  65          if v.intakeDefaultRotation ~= nil then
  66              RopeWinch.setIntakeRotation(self.ropeWinchNode, v.intakeDefaultRotation);
  67          end;
  68          if v.detachedHookIndex ~= nil then
  69              RopeWinch.setDetachedId(self.ropeWinchNode, getChild(self.id, v.detachedHookIndex));
  70          end;
  71          if v.attachedHookIndex ~= nil and v.attachedHookTargetIndex ~= nil then
  72              RopeWinch.setHook(self.ropeWinchNode, getChild(self.id, v.attachedHookIndex), getChild(self.id, v.attachedHookTargetIndex));
  73          end;
  74  
  75          -- tensioning
  76          self.ropeWinchForceMin      = v.winchForceMin or 1200;
  77          self.ropeWinchForceMax      = v.winchForceMax or 46000;
  78          self.ropeWinchForceSpeed    = 1/math.max(v.forceToggleDuration or 1, 0.1);
  79          self.ropeWinchForceLerp     = 0;
  80          self.ropeWinchForceTarget   = 0;
  81  
  82          -- motorized rotating
  83          self.winchRotateSpeed       = v.winchRotateSpeed or 30;
  84          self.winchRotateAcc         = v.winchRotateAcc or 30;
  85          self.winchCurrentRotSpeed   = 0;
  86  
  87          self.soloForwardStiffness   = v.soloForwardStiffness;
  88          self.soloSidewaysStiffness  = v.soloSidewaysStiffness;
  89  
  90          self.maxForwardStiffness    = v.maxForwardStiffness;
  91          self.maxSidewaysStiffness   = v.maxSidewaysStiffness;
  92  
  93          -- control button for connecting
  94          self.ropeWinchConnector     = getChild(self.id, v.connector or "");
  95          
  96          ControlElement.new(self.ropeWinchConnector);
  97          ControlElement.setCallback(self.ropeWinchConnector, 1,
  98              function()  self:ropeWinchConnectorCallback();          end
  99          );
 100          ControlElement.setActiveCallback(self.ropeWinchConnector,
 101              function()  return self:getRopeWinchConnectorActive();  end
 102          );
 103  
 104          ControlElement.setName(self.ropeWinchConnector, l10n.get("WinchAnchor_Attach"));
 105      end;
 106  
 107      self:setIsRopeTensed(false);
 108  end;

RopeWinch:saveToTable(tbl)

All RopeWinch features are correctly saved into the savegame (i.e. winch rotation, active forces and active anchor point).

 112  function RopeWinch:saveToTable(tbl)
 113      if tbl == nil then return end;
 114      if self.ropeWinchNode == nil then return end;
 115  
 116      local rx,ry,rz                  = getRotation(self.winchRoot);
 117  
 118      tbl.ropeWinchForceLerp          = self.ropeWinchForceLerp;
 119      tbl.ropeWinchForceTarget        = self.ropeWinchForceTarget;
 120      tbl.anchorPointName             = self.anchorPointName;
 121      tbl.winchRotationY              = ry;
 122  end;

RopeWinch:loadFromTable(tbl)

Of course, we also need to restore some data from the savegame.

Note that the vehicle will be attached to the anchor point not directly upon loading, but rather during the very next update call.

 128  function RopeWinch:loadFromTable(tbl)
 129      if tbl == nil then return end;
 130      if self.ropeWinchNode == nil then return end;
 131  
 132      if tbl.winchRotationY ~= nil then
 133          setRotationY(self.winchRoot, tbl.winchRotationY);
 134      end;
 135  
 136      -- return if rope winch is not saved correctly
 137      if tbl.ropeWinchForceLerp == nil then return end;
 138      self.ropeWinchForceLerp         = getNoNil(tbl.ropeWinchForceLerp,      self.ropeWinchForceLerp);
 139      self.ropeWinchForceTarget       = getNoNil(tbl.ropeWinchForceTarget,    self.ropeWinchForceTarget);
 140  
 141      if tbl.anchorPointName ~= nil then
 142          -- attach it at the very beginning of the next frame (when loading has already been finished)
 143          self.attachNextFrameTo      = tbl.anchorPointName;
 144      end;
 145  end;

RopeWinch:onReset()

Reset the rope winch as well if the vehicle is resetted. Of course, it also makes sense to disconnect from any anchor point in this case.

 149  function RopeWinch:onReset()
 150      if self.ropeWinchNode == nil then return end;
 151  
 152      self.ropeWinchForceLerp         = 0;
 153      self:setIsRopeTensed(false);
 154  
 155      -- disconnect from anchor
 156      if self.anchorPointName ~= nil then
 157          local anchorPoint               = RopeWinch.anchorPoints[self.anchorPointName or ""];
 158      
 159          if anchorPoint ~= nil then
 160              anchorPoint:disconnect(true);
 161          end;
 162          self:attachWinchToAnchorPoint(nil);
 163      end;
 164  
 165      AnchorPoint.activeVehicle       = nil;
 166  
 167      setRotationY(self.winchRoot, 0);
 168  end;

RopeWinch:setIsRopeTensed(isTensed)

Applies whether the rope is tensed or not, depending on isTensed (bool).

 172  function RopeWinch:setIsRopeTensed(isTensed)
 173      if self.ropeWinchNode == nil then return end;
 174  
 175      self.ropeWinchForceTarget       = isTensed and 1 or 0;
 176  
 177      local currentForce              = lerp(self.ropeWinchForceMin, self.ropeWinchForceMax, self.ropeWinchForceLerp);
 178      RopeWinch.setTensioningForce(self.ropeWinchNode, currentForce);
 179      RopeWinch.setSegmentLength(self.ropeWinchNode, lerp(0.5, 2, self.ropeWinchForceLerp));
 180  
 181      -- apply stiffness to the wheels
 182      if self.applyWheelStiffness == nil then return end;
 183  
 184      if isTensed then
 185          if self.maxForwardStiffness ~= nil then
 186              self:applyWheelStiffness(self.maxForwardStiffness, self.maxSidewaysStiffness);
 187          end;
 188      else
 189          if self.soloForwardStiffness ~= nil then
 190              self:applyWheelStiffness(self.soloForwardStiffness, self.soloSidewaysStiffness);
 191          end;
 192      end;
 193  end;

RopeWinch:attachWinchToAnchorPoint(anchorPointName)

Attach the winch to the anchor point specified in anchorPointName (string). anchorPointName can also be nil to disconnect from any active anchor point.

 197  function RopeWinch:attachWinchToAnchorPoint(anchorPointName)
 198      -- note: this function is ALWAYS called by the anchor point itself
 199      if self.ropeWinchNode == nil then return end;
 200  
 201      if self.anchorPointName ~= nil then
 202          -- disconnect from there
 203          RopeWinch.setTargetObject(self.ropeWinchNode, 0);
 204          self.anchorPointName        = nil;
 205      end;
 206  
 207      if anchorPointName == nil then
 208          -- no attacher point set, so let's loose our rope by force
 209          self:setIsRopeTensed(false);
 210          return;
 211      end;
 212  
 213      local anchorPoint               = RopeWinch.anchorPoints[anchorPointName or ""];
 214  
 215      if anchorPoint == nil then return end;
 216  
 217      -- there is an anchor point indeed existing, yay
 218      self.anchorPointName            = anchorPointName;
 219      RopeWinch.setTargetObject(self.ropeWinchNode, anchorPoint.ropeTargetId);
 220  end;

RopeWinch:update(dt)

Reads in some input and passes it on to the C# functions (if necessary) every frame.

 224  function RopeWinch:update(dt)
 225      if self.ropeWinchNode == nil then return end;
 226      
 227      if self.attachNextFrameTo ~= nil then
 228  
 229          local anchorPoint           = RopeWinch.anchorPoints[self.attachNextFrameTo];
 230  
 231          if anchorPoint ~= nil then
 232              -- this will automatically call all functions on our vehicle to attach it there
 233              anchorPoint:attach(self);
 234          end;
 235  
 236          self:setIsRopeTensed(self.ropeWinchForceTarget > 0.5);
 237          self.attachNextFrameTo      = nil;
 238      end;
 239      
 240      if not self.isActive then return end;
 241  
 242      local rotSpeedTarget            = 0;
 243  
 244      if self.anchorPointName == "player" then
 245          if not PlayerController.isActive then
 246              -- urgently disconnect!
 247              self:attachWinchToAnchorPoint(nil);
 248          end;
 249      end;
 250      
 251      if self:getIsInputActive() then
 252          if self.anchorPointName ~= nil and self.anchorPointName ~= "player" then
 253              if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_TenseWinch) then
 254                  self:setIsRopeTensed(self.ropeWinchForceTarget < 0.5);
 255              end;
 256              g_GUI:addKeyHint(InputMapper.VehicleSnowcat_TenseWinch,     l10n.get(self.ropeWinchForceTarget > 0.5    and "Input_VehicleSnowcat_TenseWinch1"  or "Input_VehicleSnowcat_TenseWinch0"));
 257          end;
 258  
 259          if self.anchorPointName == nil then
 260              -- player may now move the winch if he wants to
 261              if InputMapper:getKey(InputMapper.VehicleSnowcat_TurnWinchLeft) then
 262                  -- turn left
 263                  rotSpeedTarget      = -self.winchRotateSpeed;
 264              elseif InputMapper:getKey(InputMapper.VehicleSnowcat_TurnWinchRight) then
 265                  -- turn right
 266                  rotSpeedTarget      = self.winchRotateSpeed;
 267              end;
 268          end;
 269      end;
 270  
 271      -- update winch force
 272      if math.abs(self.ropeWinchForceTarget - self.ropeWinchForceLerp) > 1e-3 then
 273          local scale                 = clamp(0.2, 1, 1-self.ropeWinchForceLerp);
 274          self.ropeWinchForceLerp     = Utils.moveTowards(self.ropeWinchForceLerp, self.ropeWinchForceTarget, scale * self.ropeWinchForceSpeed*dt);
 275  
 276          local currentForce          = lerp(self.ropeWinchForceMin, self.ropeWinchForceMax, self.ropeWinchForceLerp);
 277          RopeWinch.setTensioningForce(self.ropeWinchNode, currentForce);
 278          RopeWinch.setSegmentLength(self.ropeWinchNode, lerp(0.5, 2, self.ropeWinchForceLerp));
 279      end;
 280  
 281      if self.anchorPointName ~= nil then
 282          rotSpeedTarget              = 0;
 283          self.winchCurrentRotSpeed   = 0;
 284      end;
 285  
 286      self.winchCurrentRotSpeed       = Utils.moveTowards(self.winchCurrentRotSpeed, rotSpeedTarget, self.winchRotateAcc*3*dt);
 287  
 288      if math.abs(self.winchCurrentRotSpeed) > 1e-3 then
 289          local rx, ry, rz            = getRotation(self.winchRoot);
 290          setRotationY(self.winchRoot, ry + self.winchCurrentRotSpeed * dt);
 291      end;
 292  end;

RopeWinch:getMayRopeWinchDisconnect()

Returns whether the winch may be disconnected right now (bool).

 296  function RopeWinch:getMayRopeWinchDisconnect()
 297      if self.anchorPointName ~= nil and self.anchorPointName ~= "player" then
 298          -- we're attached to a real attacher
 299          return self.ropeWinchForceLerp < 0.1;
 300      end;
 301      return true;
 302  end;

RopeWinch:getRopeWinchConnectorActive()

Returns whether the control element located at the winch intake shall be active or not (bool).

 306  function RopeWinch:getRopeWinchConnectorActive()
 307      if self.anchorPointName ~= nil and self.anchorPointName ~= "player" then
 308          -- don't click there if we're attached
 309          return false;
 310      end;
 311      if self.anchorPointName == "player" then
 312          -- player may always decouple!
 313          return true;
 314      end;
 315  
 316      -- also don't click there if there's force on the rope
 317      if self.ropeWinchForceLerp > 0.1 then
 318          return false;
 319      end;
 320  
 321      -- show only if player controller is active
 322      return PlayerController.isActive and (AnchorPoint.activeVehicle == nil or (AnchorPoint.activeVehicle == self and self.anchorPointName == nil));
 323  end;

RopeWinch:ropeWinchConnectorCallback()

This is called every time the player clicks onto the control element at the winch intake. It may indicate that the player either wants to take the rope (and attach it to an anchor point) or to take in the rope.

 328  function RopeWinch:ropeWinchConnectorCallback()
 329      -- rope connector was hit
 330      if AnchorPoint.activeVehicle == self then
 331          -- turn off winch, no longer available for attaching
 332          self:attachWinchToAnchorPoint(nil);
 333          AnchorPoint.activeVehicle   = nil;
 334  
 335      else
 336          -- player wants to attach us
 337          AnchorPoint.activeVehicle   = self;
 338          self:attachWinchToAnchorPoint("player");
 339  
 340          if g_scenario.tutorialAgent ~= nil then
 341              g_scenario.tutorialAgent:callByScript("attachRopeWinch");
 342          end;
 343      end;
 344  end;

RopeWinch:onEnter()

Shows an info message when the player enters a winch vehicle for the first time.

 348  function RopeWinch:onEnter()
 349      -- shoot out a help message
 350      if g_scenario.tutorialAgent ~= nil then
 351          g_scenario.tutorialAgent:callByScript("winchSnowcat");
 352      end;
 353  end;

All contents of this page may be used for modding use for Winter Resort Simulator only. Any use exceeding this regulation is not permitted.

Copyright (C) HR Innoways, 2020. All Rights Reserved.