meta data for this page
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 self.setWinchRootRotation = VehicleManager:newFunction("setWinchRootRotation"); 52 53 self.getRopeWinchConnectorActive= RopeWinch.getRopeWinchConnectorActive; 54 self.getMayRopeWinchDisconnect = RopeWinch.getMayRopeWinchDisconnect; 55 56 if dataTable.ropeWinch ~= nil then 57 local v = dataTable.ropeWinch; 58 self.winchRoot = getChild(self.id, v.winchRoot or ""); 59 self.winchIntake = getChild(self.id, v.winchIntake or ""); 60 local ropeDiameter = v.ropeDiameter or 0.011; 61 local ropeWeightPerMeter = v.weightPerMeter or 45; 62 63 self.ropeWinchNode = RopeWinch.new(self.id, self.winchRoot, self.winchIntake, ropeDiameter, ropeWeightPerMeter); 64 self.anchorPointName = nil; 65 self.currentSetAnchorPoint = ""; 66 67 if v.intakeDefaultRotation ~= nil then 68 RopeWinch.setIntakeRotation(self.ropeWinchNode, v.intakeDefaultRotation); 69 end; 70 if v.detachedHookIndex ~= nil then 71 RopeWinch.setDetachedId(self.ropeWinchNode, getChild(self.id, v.detachedHookIndex)); 72 end; 73 if v.attachedHookIndex ~= nil and v.attachedHookTargetIndex ~= nil then 74 RopeWinch.setHook(self.ropeWinchNode, getChild(self.id, v.attachedHookIndex), getChild(self.id, v.attachedHookTargetIndex)); 75 self.attachedHookIndex = v.attachedHookIndex; 76 end; 77 78 RopeWinch.setIntakeParameters(self.ropeWinchNode, 79 getNoNil(v.intakeDiameter, 0.35)/2, 80 getNoNil(v.intakeMinLimit, -80), 81 getNoNil(v.intakeMaxLimit, 80) 82 ); 83 84 85 -- tensioning 86 self.ropeWinchForceMin = v.winchForceMin or 1200; 87 self.ropeWinchForceMax = v.winchForceMax or 46000; 88 self.ropeWinchForceSpeed = 1/math.max(v.forceToggleDuration or 1, 0.1); 89 self.ropeWinchForceLerp = 0; 90 self.ropeWinchForceTarget = 0; 91 92 -- motorized rotating 93 self.winchRotateSpeed = v.winchRotateSpeed or 30; 94 self.winchRotateAcc = v.winchRotateAcc or 30; 95 self.winchCurrentRotSpeed = 0; 96 97 self.soloForwardStiffness = v.soloForwardStiffness; 98 self.soloSidewaysStiffness = v.soloSidewaysStiffness; 99 100 self.maxForwardStiffness = v.maxForwardStiffness; 101 self.maxSidewaysStiffness = v.maxSidewaysStiffness; 102 103 -- control button for connecting 104 self.ropeWinchConnector = getChild(self.id, v.connector or ""); 105 106 -- load winch wheels 107 if v.winchWheels ~= nil then 108 self.winchWheels = {}; 109 for kWheel, vWheel in pairs(v.winchWheels) do 110 local wheel = { 111 wheel = getChild(self.id, vWheel.wheel), 112 diameter = getNoNil(vWheel.diameter, 0), 113 reverse = getNoNil(vWheel.reverse, false), 114 axis = getNoNil(vWheel.axis, 0), 115 }; 116 table.insert(self.winchWheels, wheel); 117 end; 118 end; 119 120 -- guidance system 121 if v.guidanceAnimation ~= nil then 122 self.guidanceAnimation = getChild(self.id, v.guidanceAnimation); 123 end; 124 if g_scenario.player ~= nil then 125 self.localPlayerIdentifier = g_scenario.player; 126 end; 127 ControlElement.new(self.ropeWinchConnector); 128 ControlElement.setCallback(self.ropeWinchConnector, 1, 129 function() self:ropeWinchConnectorCallback(); end 130 ); 131 ControlElement.setActiveCallback(self.ropeWinchConnector, 132 function() return self:getRopeWinchConnectorActive(); end 133 ); 134 135 ControlElement.setName(self.ropeWinchConnector, l10n.get("WinchAnchor_Attach")); 136 end; 137 self:setIsRopeTensed(false, true); 138 end;
RopeWinch:destroy()
Destroys the control element again.
141 function RopeWinch:destroy() 142 if self.ropeWinchConnector ~= nil then 143 ControlElement.destroy(self.ropeWinchConnector); 144 end; 145 146 self:attachWinchToAnchorPoint(nil, true); 147 end;
RopeWinch:saveToTable(tbl)
All RopeWinch features are correctly saved into the savegame (i.e. winch rotation, active forces and active anchor point).
151 function RopeWinch:saveToTable(tbl) 152 if tbl == nil then return end; 153 if self.ropeWinchNode == nil then return end; 154 155 local rx,ry,rz = getRotation(self.winchRoot); 156 157 tbl.ropeWinchForceLerp = self.ropeWinchForceLerp; 158 tbl.ropeWinchForceTarget = self.ropeWinchForceTarget; 159 tbl.winchRotationY = ry; 160 161 if self.anchorPointName ~= self.localPlayerIdentifier then 162 tbl.anchorPointName = self.anchorPointName; 163 end; 164 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.
170 function RopeWinch:loadFromTable(tbl) 171 if tbl == nil then return end; 172 if self.ropeWinchNode == nil then return end; 173 174 if tbl.winchRotationY ~= nil then 175 self:setWinchRootRotation(tbl.winchRotationY); 176 end; 177 178 -- return if rope winch is not saved correctly 179 if tbl.ropeWinchForceLerp == nil then return end; 180 self.ropeWinchForceLerp = getNoNil(tbl.ropeWinchForceLerp, self.ropeWinchForceLerp); 181 self.ropeWinchForceTarget = getNoNil(tbl.ropeWinchForceTarget, self.ropeWinchForceTarget); 182 183 if tbl.anchorPointName ~= nil and tbl.anchorPointName ~= self.localPlayerIdentifier then 184 -- attach it at the very beginning of the next frame (when loading has already been finished) 185 self.attachNextFrameTo = tbl.anchorPointName; 186 end; 187 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.
191 function RopeWinch:onReset() 192 if self.ropeWinchNode == nil then return end; 193 194 self.ropeWinchForceLerp = 0; 195 self:setIsRopeTensed(false); 196 197 -- disconnect from anchor 198 if self.anchorPointName ~= nil then 199 local anchorPoint = RopeWinch.anchorPoints[self.anchorPointName or ""]; 200 201 if anchorPoint ~= nil then 202 anchorPoint:disconnect(true); 203 end; 204 self:attachWinchToAnchorPoint(nil); 205 end; 206 207 if AnchorPoint.activeVehicle == self then 208 AnchorPoint.activeVehicle = nil; 209 end; 210 211 self:setWinchRootRotation(0); 212 end;
RopeWinch:setIsRopeTensed(isTensed, noEvent)
Applies whether the rope is tensed or not, depending on isTensed
(bool).
216 function RopeWinch:setIsRopeTensed(isTensed, noEvent) 217 if self.ropeWinchNode == nil then return end; 218 219 self.ropeWinchForceTarget = isTensed and 1 or 0; 220 221 local currentForce = lerp(self.ropeWinchForceMin, self.ropeWinchForceMax, self.ropeWinchForceLerp); 222 RopeWinch.setTensioningForce(self.ropeWinchNode, currentForce); 223 RopeWinch.setSegmentLength(self.ropeWinchNode, lerp(0.5, 2, self.ropeWinchForceLerp)); 224 225 -- apply stiffness to the wheels 226 if self.applyWheelStiffness == nil then return end; 227 228 if isTensed then 229 if self.maxForwardStiffness ~= nil then 230 self:applyWheelStiffness(self.maxForwardStiffness, self.maxSidewaysStiffness); 231 end; 232 else 233 if self.soloForwardStiffness ~= nil then 234 self:applyWheelStiffness(self.soloForwardStiffness, self.soloSidewaysStiffness); 235 end; 236 end; 237 238 if not noEvent then 239 EventSetIsRopeTensed:send(self, isTensed); 240 end; 241 end;
RopeWinch:attachWinchToAnchorPoint(anchorPointName, noEvent)
Attach the winch to the anchor point specified in anchorPointName
(string). anchorPointName
can also be nil to disconnect from any active anchor point.
245 function RopeWinch:attachWinchToAnchorPoint(anchorPointName, noEvent) 246 247 -- note: this function is ALWAYS called by the anchor point itself 248 if self.ropeWinchNode == nil then return end; 249 250 if self.anchorPointName ~= nil then 251 local oldAnchorPoint = RopeWinch.anchorPoints[self.anchorPointName]; 252 253 -- disconnect from there 254 RopeWinch.setTargetObject(self.ropeWinchNode, 0); 255 self.anchorPointName = nil; 256 257 if oldAnchorPoint ~= nil and oldAnchorPoint.connectedVehicle == self then 258 oldAnchorPoint.connectedVehicle = nil; 259 end; 260 end; 261 262 self.anchorPointName = anchorPointName; 263 self.currentSetAnchorPoint = anchorPointName; 264 265 if not noEvent then 266 EventSetRopeWinchConnection:send(self, anchorPointName); 267 end; 268 269 if anchorPointName == "" then 270 self.anchorPointName = nil; 271 end; 272 273 if anchorPointName == nil then 274 -- no attacher point set, so let's loose our rope by force 275 self:setIsRopeTensed(false, noEvent); 276 end; 277 278 279 local anchorPoint = RopeWinch.anchorPoints[anchorPointName]; 280 281 if anchorPoint ~= nil then 282 -- there is an anchor point indeed existing, yay 283 RopeWinch.setTargetObject(self.ropeWinchNode, anchorPoint.ropeTargetId); 284 anchorPoint.connectedVehicle = self; 285 end; 286 end;
RopeWinch:update(dt)
Reads in some input and passes it on to the C# functions (if necessary) every frame.
290 function RopeWinch:update(dt) 291 -- in case the player will by synched after the vehiclePostResync 292 if self.localPlayerIdentifier == nil then 293 self.localPlayerIdentifier = g_scenario.player; 294 end; 295 296 if self.ropeWinchNode == nil then return end; 297 298 if self.attachNextFrameTo ~= nil then 299 300 local anchorPoint = RopeWinch.anchorPoints[self.attachNextFrameTo]; 301 302 if anchorPoint ~= nil then 303 -- this will automatically call all functions on our vehicle to attach it there 304 anchorPoint:attach(self); 305 end; 306 307 self:setIsRopeTensed(self.ropeWinchForceTarget > 0.5); 308 self.attachNextFrameTo = nil; 309 end; 310 311 -- read winch rope length for wheels 312 local ropeLength = RopeWinch.getRopeLength(self.ropeWinchNode) / 2; 313 314 -- update winchWheels if existing 315 if self.winchWheels ~= nil then 316 for kWheel, vWheel in pairs(self.winchWheels) do 317 if vWheel.reverse then 318 if vWheel.axis == 0 or vWheel.axis == 1 then 319 setRotation(vWheel.wheel, ((ropeLength)/ vWheel.diameter * 360 )/ math.pi, 180, 180); 320 elseif vWheel.axis == 2 then 321 setRotation(vWheel.wheel, 0, -((ropeLength)/ vWheel.diameter * 360) / math.pi, 0); 322 else 323 setRotation(vWheel.wheel, 180, 180, ((ropeLength)/ vWheel.diameter * 360) / math.pi); 324 end; 325 else 326 if vWheel.axis == 0 or vWheel.axis == 1 then 327 setRotation(vWheel.wheel, ((ropeLength)/ vWheel.diameter * 360) / math.pi, 0, 0); 328 elseif vWheel.axis == 2 then 329 setRotation(vWheel.wheel, 0, ((ropeLength)/ vWheel.diameter * 360) / math.pi, 0); 330 else 331 setRotation(vWheel.wheel, 0, 0, ((ropeLength)/ vWheel.diameter * 360) / math.pi); 332 end; 333 end; 334 end; 335 end; 336 337 local scope = math.pi * 0.946563; 338 local sliderValue = Utils.pingPong((ropeLength / (scope* 18)), 1.0); 339 340 if self.guidanceAnimation ~= nil then 341 Animation.sampleTime(self.guidanceAnimation, sliderValue, "WinchSlider"); 342 end; 343 344 if self.localPlayerIdentifier ~= nil and self.anchorPointName == self.localPlayerIdentifier and not self.localPlayerIdentifier:getIsLocalPlayerEntered() then 345 self:attachWinchToAnchorPoint(nil); 346 end; 347 348 if not self.isActive then return end; 349 350 local rotSpeedTarget = 0; 351 352 if self.anchorPointName == self.localPlayerIdentifier then 353 if g_scenario.player ~= nil and not g_scenario.player.isActive then 354 -- urgently disconnect! 355 self:attachWinchToAnchorPoint(nil); 356 end; 357 end; 358 359 if self:getIsInputActive() then 360 if self.anchorPointName ~= nil and self.anchorPointName ~= self.localPlayerIdentifier then 361 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_TenseWinch) then 362 self:setIsRopeTensed(self.ropeWinchForceTarget < 0.5); 363 end; 364 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_TenseWinch, l10n.get(self.ropeWinchForceTarget > 0.5 and "Input_VehicleSnowcat_TenseWinch1" or "Input_VehicleSnowcat_TenseWinch0")); 365 end; 366 367 if self.anchorPointName == nil then 368 -- player may now move the winch if he wants to 369 if InputMapper:getKey(InputMapper.VehicleSnowcat_TurnWinchLeft) then 370 -- turn left 371 rotSpeedTarget = -self.winchRotateSpeed; 372 elseif InputMapper:getKey(InputMapper.VehicleSnowcat_TurnWinchRight) then 373 -- turn right 374 rotSpeedTarget = self.winchRotateSpeed; 375 end; 376 end; 377 end; 378 379 -- update winch force 380 if math.abs(self.ropeWinchForceTarget - self.ropeWinchForceLerp) > 1e-3 then 381 local scale = clamp(0.2, 1, 1-self.ropeWinchForceLerp); 382 self.ropeWinchForceLerp = Utils.moveTowards(self.ropeWinchForceLerp, self.ropeWinchForceTarget, scale * self.ropeWinchForceSpeed*dt); 383 384 local currentForce = lerp(self.ropeWinchForceMin, self.ropeWinchForceMax, self.ropeWinchForceLerp); 385 RopeWinch.setTensioningForce(self.ropeWinchNode, currentForce); 386 RopeWinch.setSegmentLength(self.ropeWinchNode, lerp(0.5, 2, self.ropeWinchForceLerp)); 387 end; 388 389 if self.anchorPointName ~= nil then 390 rotSpeedTarget = 0; 391 self.winchCurrentRotSpeed = 0; 392 end; 393 394 self.winchCurrentRotSpeed = Utils.moveTowards(self.winchCurrentRotSpeed, rotSpeedTarget, self.winchRotateAcc*3*dt); 395 396 if math.abs(self.winchCurrentRotSpeed) > 1e-3 then 397 local rx, ry, rz = getRotation(self.winchRoot); 398 self:setWinchRootRotation(ry + self.winchCurrentRotSpeed * dt); 399 end; 400 end;
RopeWinch:getMayRopeWinchDisconnect()
Returns whether the winch may be disconnected right now (bool).
404 function RopeWinch:getMayRopeWinchDisconnect() 405 if self.anchorPointName ~= nil and self.anchorPointName ~= self.localPlayerIdentifier then 406 -- we're attached to a real attacher 407 return self.ropeWinchForceLerp < 0.1; 408 end; 409 return true; 410 end;
RopeWinch:getRopeWinchConnectorActive()
Returns whether the control element located at the winch intake shall be active or not (bool).
414 function RopeWinch:getRopeWinchConnectorActive() 415 if self.anchorPointName ~= nil and self.anchorPointName ~= self.localPlayerIdentifier then 416 -- don't click there if we're attached 417 return false; 418 end; 419 if self.anchorPointName == self.localPlayerIdentifier then 420 -- player may always decouple! 421 return true; 422 end; 423 424 -- also don't click there if there's force on the rope 425 if self.ropeWinchForceLerp > 0.1 then 426 return false; 427 end; 428 -- show only if player controller is active 429 return --[[ (g_scenario.player ~= nil and g_scenario.player.isActive) and 430 (AnchorPoint.activeVehicle == nil or (AnchorPoint.activeVehicle == self and self.anchorPointName == nil)); 431 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.
435 function RopeWinch:ropeWinchConnectorCallback() 436 -- rope connector was hit 437 if AnchorPoint.activeVehicle == self then 438 -- turn off winch, no longer available for attaching 439 self:attachWinchToAnchorPoint(nil); 440 AnchorPoint.activeVehicle = nil; 441 else 442 -- player wants to attach us 443 AnchorPoint.activeVehicle = self; 444 self:attachWinchToAnchorPoint(self.localPlayerIdentifier); 445 446 if g_scenario.tutorialAgent ~= nil then 447 g_scenario.tutorialAgent:callByScript("attachRopeWinch"); 448 end; 449 end; 450 end;
RopeWinch:onEnter(player, isLocalPlayer)
Shows an info message when the player enters a winch vehicle for the first time.
454 function RopeWinch:onEnter(player, isLocalPlayer) 455 -- shoot out a help message 456 if isLocalPlayer and g_scenario.tutorialAgent ~= nil then 457 g_scenario.tutorialAgent:callByScript("winchSnowcat"); 458 end; 459 end;
RopeWinch:setWinchRootRotation(rotation, noEvent)
Sets the winchRoot to the rotationY value.
463 function RopeWinch:setWinchRootRotation(rotation, noEvent) 464 setRotationY(self.winchRoot, rotation); 465 466 if not noEvent then 467 EventSetRopeWinchRotation:send(self, rotation); 468 end; 469 end;
RopeWinch:writeResync()
Resynchronizes all variables when a new player joins the game. The data sent by writeResync
will be received by readResync
.
473 function RopeWinch:writeResync() 474 if self.winchRoot == nil then 475 return; 476 end; 477 478 local rx ,ry, rz = getRotation(self.winchRoot); 479 streamWriteFloat(ry); 480 481 streamWriteBool(self.isTensed); 482 483 streamWriteFloat(self.ropeWinchForceLerp or 0); 484 485 end;
RopeWinch:readResync()
Resynchronizes all variables when a new player joins the game. The data sent by writeResync
will be received by readResync
.
488 function RopeWinch:readResync() 489 if self.winchRoot == nil then 490 return; 491 end; 492 493 local rotY = streamReadFloat(); 494 self:setWinchRootRotation(rotY, true); 495 496 local tensed = streamReadBool(); 497 self.ropeWinchForceLerp = streamReadFloat(); 498 self:setIsRopeTensed(tensed, true); 499 end;
RopeWinch:postWriteResync()
postWriteResync
is called after all writeResync
calls of all entities have been processed. Do not use any streamWrite…
functions in here, instead please send full events. These events will be executed right after readResync
on the client, i.e. all entities will already be set up properly on the client before the events are executed.
We use this function to attach the rope hook for a newly joined player.
504 function RopeWinch:postWriteResync() 505 EventSetRopeWinchConnection:send(self, self.currentSetAnchorPoint); 506 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator - Season 2 only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2021. All Rights Reserved.