openclonk/planet/Objects.ocd/Items.ocd/Tools.ocd/Ropeladder.ocd/Script.c

462 lines
13 KiB
C

/*
Ropeladder
Author: Randrian
A ladder consisting of ropesegments. The physics is done completly intern with point masses.
Verlet integration and simple stick constraints are used for the rope physics.
The segments are an extra object, which handels the graphics and the detection of the ladder by the clonk.
Interface:
Unroll(int dir, int unrolldir, int length); // dir == -1 expand to the left, dir == 1 expand right;
// unrolldir == COMD_Down, COMD_Right, COMD_Left or COMD_Up specifies where to adjust to a wall
// length, length of segments, default 15
*/
#include Library_Rope
static const Ladder_MaxParticles = 15;//30;//15*3;
static const Ladder_Iterations = 10;
static const Ladder_Precision = 100;
static const Ladder_SegmentLength = 5;//2;
local particles;
local segments;
local TestArray;
local MaxSegmentCount;
local SwitchRopes;
local MirrorSegments;
local UnrollDir;
local ParticleCount;
local grabber;
public func ControlUse(object clonk, int x, int y)
{
if(!clonk->GetContact(-1)) return true;
// Unroll dir
var dir = -1;
if(x > 0) dir = 1;
if(clonk->GetAction() == "Scale")
{
if(clonk->GetDir() == 0)
{
Exit(0, 8);
Unroll(1, COMD_Right);
}
else
{
Exit(0, 8);
Unroll(-1, COMD_Left);
}
}
else if(clonk->GetAction() == "Hangle")
{
Exit(0, 0);
Unroll(dir, COMD_Up);
}
else
{
Exit(0, 5);
Unroll(dir, COMD_Down);
}
return true;
}
public func UpdateSegmentOverlays()
{
for(var i = 1; i < GetLength(segments); i++)
{
segments[i]->SetGraphics("Line", GetID(), 2, 1);
segments[i]->SetGraphics("Line", GetID(), 3, 1);
segments[i]->SetGraphics(nil, nil, 4);
segments[i]->SetGraphics(nil, nil, 5);
if(i > 1)
segments[i]->SetGraphics("NoRope", segments[i]->GetID(), 4, 1);
if(i == GetLength(segments)-1)
segments[i]->SetGraphics("NoRope", segments[i]->GetID(), 5, 1);
if(i == 1)
{
segments[i]->SetGraphics("Anchor", GetID(), 1, 1);
segments[i]->SetGraphics("AnchorOverlay", GetID(), 5, 1);
}
}
}
func TestMoveOut(xdir, ydir)
{
if(!Stuck()) return;
for(var i = 0; i < 8; i++)
{
if(!GBackSolid(i*xdir, i*ydir))
{
SetPosition(GetX()+i*xdir, GetY()+i*ydir);
if(!Stuck())
break;
SetPosition(GetX()-i*xdir, GetY()-i*ydir);
}
}
}
public func Unroll(int dir, int unrolldir, int length)
{
if(!unrolldir) unrolldir = COMD_Down;
SwitchRopes = 0;
// Unroll dir
if(unrolldir == COMD_Left || unrolldir == COMD_Right)
{
var xdir = 1;
if(unrolldir == COMD_Right)
{
xdir = -1;
}
var x1 = 0;
var x2 = 0;
var x0 = 0;
var y1 =-5;
var y2 = 5;
while(!GBackSolid(x0, 0) && Abs(x0) < 10) x0 += xdir;
while(!GBackSolid(x1, y1) && Abs(x1) < 10) x1 += xdir;
while(!GBackSolid(x2, y2) && Abs(x2) < 10) x2 += xdir;
SetPosition(GetX()+x0-2*xdir, GetY());
SetR(90*xdir+Angle(x1, y1, x2, y2));
}
else if(unrolldir == COMD_Up)
{
var x1 =-2;
var x2 = 2;
var y1 = 0;
var y2 = 0;
var y0 = 0;
while(!GBackSolid( 0, y0) && Abs(y0) < 10) y0--;
while(!GBackSolid(x1, y1) && Abs(y1) < 10) y1--;
while(!GBackSolid(x2, y2) && Abs(y2) < 10) y2--;
SetPosition(GetX(), GetY()+y0+2);
SetR(-90+Angle(x2, y2, x1, y1));
SwitchRopes = 1;
}
else
{
var x1 =-2;
var x2 = 2;
var y1 = 0;
var y2 = 0;
var y0 = 0;
while(!GBackSolid( 0, y0) && Abs(y0) < 10) y0++;
while(!GBackSolid(x1, y1) && Abs(y1) < 10) y1++;
while(!GBackSolid(x2, y2) && Abs(y2) < 10) y2++;
SetPosition(GetX(), GetY()+y0-2);
SetR(90+Angle(x2, y2, x1, y1));
}
if(!length)
{
if(MaxSegmentCount == nil)
MaxSegmentCount = Ladder_MaxParticles;
}
else MaxSegmentCount = length;
DoUnroll(dir);
}
public func MakeBridge(obj1, obj2)
{
MirrorSegments = 1;
SetProperty("Collectible", 0);
StartRopeConnect(obj1, obj2);
AddEffect("IntHang", this, 1, 1, this);
}//MakeBridge(Object(221), Object(150))
protected func DoUnroll(dir)
{
MirrorSegments = dir;
UnrollDir = dir;
SetAction("Hanging");
SetProperty("Collectible", 0);
// TestArray = [[0, 1], [1, 0], [1, 1], [0, 2], [1, 2], [2, 0], [2, 1], [2, 2], [0, 3], [1, 3], [2, 3], [3, 0], [3, 1], [3, 2], [0, 4], [1, 4], [2, 4], [3, 3], [4, 0], [4, 1], [4, 2], [0, 5], [1, 5], [2, 5], [3, 4], [3, 5], [4, 3], [4, 4], [5, 0], [5, 1], [5, 2], [5, 3], [0, 6], [1, 6], [2, 6], [3, 6], [4, 5], [5, 4], [6, 0], [6, 1], [6, 2], [6, 3], [0, 7], [1, 7], [2, 7], [3, 7], [4, 6], [5, 5], [5, 6], [6, 4], [6, 5], [7, 0], [7, 1], [7, 2], [7, 3], [0, 8], [1, 8], [2, 8], [3, 8], [4, 7], [4, 8], [5, 7], [6, 6], [7, 4], [7, 5], [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [0, 9], [1, 9], [2, 9], [3, 9], [4, 9], [5, 8], [6, 7], [7, 6], [7, 7], [8, 5], [9, 0], [9, 1], [9, 2], [9, 3], [9, 4]];
grabber = CreateObject(Ropeladder_Grabber);
grabber->SetAction("Attach", this);
StartRope();
AddEffect("IntHang", this, 1, 1, this);
AddEffect("UnRoll", this, 1, 2, this);
}
func StartRollUp() { RemoveEffect("UnRoll", this); AddEffect("RollUp", this, 1, 1, this); }
func FxUnRollTimer()
{
if(ParticleCount == MaxSegmentCount)
{
if(GetActTime() < MaxSegmentCount*2*2) return;
// If it wasn't possible to acchieve at least half the full length we pull in again
if( -(particles[0][0][1]-particles[ParticleCount-1][0][1]) < (ParticleCount*Ladder_SegmentLength*Ladder_Precision)/2)
StartRollUp();
return -1;
}
AddSegment(UnrollDir*Ladder_Precision, 0);
}
func FxRollUpTimer() { if(ParticleCount == 0) return -1; RemoveSegment(); }
func FxIntHangTimer()
{
TimeStep();
if(!Stuck()) TestLength();
}
func TestLength()
{
if(GetActTime() < 36) return;
// If it wasn't possible to acchieve at least half the full length we pull in again
if( -(particles[0][0][1]-particles[ParticleCount-1][0][1]) < (ParticleCount*5*Ladder_Precision)/2)
StartRollUp();
}
/* --------------------- Callbacks form the rope ---------------------- */
/* To be overloaded for special segment behaviour */
private func CreateSegment(int index, object previous)
{
var segment;
if(index == 0)
{
segment = CreateObject(Ropeladder_Segment);
segment->SetGraphics("None");
segment->SetMaster(this, 0);
}
else
{
segment = CreateObject(Ropeladder_Segment);
segment->SetMaster(this, ParticleCount);
segment->SetNextLadder(previous);
previous->SetPreviousLadder(segment);
segment->SetGraphics("None");
}
return segment;
}
private func DeleteSegment(object segment, previous)
{
if(segment)
segment->RemoveObject();
if(previous)
previous->SetPreviousLadder(nil);
}
/* When the last segment is removed */
private func RopeRemoved()
{
RemoveEffect("IntHang", this);
SetCategory(C4D_Object);
SetAction("Idle");
SetProperty("Collectible", 1);
// Try to move the Ropeladder somewhere out if it is stuck
TestMoveOut( 0, -1); // Up
TestMoveOut( 0, +1); // Down
TestMoveOut(-1, 0); // Left
TestMoveOut(+1, 0); // Right
grabber->RemoveObject();
SetGraphics("", GetID());
}
/* --------------------- Graphics of segments ---------------------- */
func UpdateLines()
{
var oldangle;
for(var i=1; i < ParticleCount; i++)
{
// Update the Position of the Segment
segments[i]->SetPosition(GetPartX(i), GetPartY(i));
// Calculate the angle to the previous segment
var angle;
if(i >= 1)
{
angle = Angle(particles[i][0][0], particles[i][0][1], particles[i-1][0][0], particles[i-1][0][1]);
segments[i]->SetAngle(angle);
}
// Every segment has not its graphics, but the graphics of the previous segment (or achor for the first)
// Otherwise the drawing order would be wrong an we would get lines over segments
// Draw the segment as an overlay for the following segment (only the last segment has two graphics (its and the previous)
if(i > 1)
SetLineTransform(segments[i], -oldangle, particles[i-1][0][0]*10-GetPartX(i)*1000,particles[i-1][0][1]*10-GetPartY(i)*1000, 1000, 4, MirrorSegments );
if(i == ParticleCount-1)
SetLineTransform(segments[i], -angle, particles[i][0][0]*10-GetPartX(i)*1000,particles[i][0][1]*10-GetPartY(i)*1000, 1000, 5, MirrorSegments );
// The first segment has to draw the achor too
if(i == 1)
{
SetLineTransform(segments[i], -GetR(), GetX()*1000-GetPartX(i)*1000, GetY()*1000-GetPartY(i)*1000, 1000, 1);
SetLineTransform(segments[i], -GetR(), GetX()*1000-GetPartX(i)*1000, GetY()*1000-GetPartY(i)*1000, 1000, 5);
}
// Draw the left line
var start = GetRopeConnetPosition(i, 0, 0, angle, oldangle);
var end = GetRopeConnetPosition(i, 0, 1, angle, oldangle);
var diff = Vec_Sub(end,start);
var diffangle = Vec_Angle(diff, [0,0]);
var point = Vec_Add(start, Vec_Div(diff, 2));
var length = Vec_Length(diff)*1000/Ladder_Precision/8;
SetLineTransform(segments[i], -diffangle, point[0]*10-GetPartX(i)*1000,point[1]*10-GetPartY(i)*1000, length, 2 );
// Draw the right line
var start = GetRopeConnetPosition(i, 1, 0, angle, oldangle);
var end = GetRopeConnetPosition(i, 1, 1, angle, oldangle);
var diff = Vec_Sub(end,start);
var diffangle = Vec_Angle(diff, [0,0]);
var point = Vec_Add(start, Vec_Div(diff, 2));
var length = Vec_Length(diff)*1000/Ladder_Precision/8;
SetLineTransform(segments[i], -diffangle, point[0]*10-GetPartX(i)*1000,point[1]*10-GetPartY(i)*1000, length, 3 );
// Remember the angle
oldangle = angle;
}
}
static const Ropeladder_Segment_LeftXOffset = 200;
static const Ropeladder_Segment_RightXOffset = -100;
func GetRopeConnetPosition(int index, bool fRight, bool fEnd, int angle, int oldangle)
{
if(SwitchRopes && index == 1 && fEnd == 0) fRight = !fRight;
if(!(index == 1 && fEnd == 0) && MirrorSegments == -1) fRight = !fRight;
if(fEnd == 0)
{
var start = [0,0];
if(fRight == 0)
{
if(index >= 2)
{
start = particles[index-1][0][:];
start[0] += -Cos(oldangle, Ropeladder_Segment_LeftXOffset*MirrorSegments);
start[1] += -Sin(oldangle, Ropeladder_Segment_LeftXOffset*MirrorSegments);
}
else
{
start = [GetX()*Ladder_Precision, GetY()*Ladder_Precision];
start[0] += -Cos(GetR(), 188)+Sin(GetR(), 113);
start[1] += -Sin(GetR(), 188)-Cos(GetR(), 113);
}
}
else
{
if(index >= 2)
{
start = particles[index-1][0][:];
start[0] += -Cos(oldangle, Ropeladder_Segment_RightXOffset*MirrorSegments);
start[1] += -Sin(oldangle, Ropeladder_Segment_RightXOffset*MirrorSegments);
}
else
{
start = [GetX()*Ladder_Precision, GetY()*Ladder_Precision];
start[0] += +Cos(GetR(), 188)+Sin(GetR(), 113);
start[1] += +Sin(GetR(), 188)-Cos(GetR(), 113);
}
}
return start;
}
else
{
var end = [0,0];
if(fRight == 0)
{
end = particles[index][0][:];
end[0] += -Cos(angle, Ropeladder_Segment_LeftXOffset*MirrorSegments);
end[1] += -Sin(angle, Ropeladder_Segment_LeftXOffset*MirrorSegments);
}
else
{
end = particles[index][0][:];
end[0] += -Cos(angle, Ropeladder_Segment_RightXOffset*MirrorSegments);
end[1] += -Sin(angle, Ropeladder_Segment_RightXOffset*MirrorSegments);
}
return end;
}
}
func SetLineTransform(obj, int r, int xoff, int yoff, int length, int layer, int MirrorSegments) {
if(!MirrorSegments) MirrorSegments = 1;
var fsin=Sin(r, 1000), fcos=Cos(r, 1000);
// set matrix values
obj->SetObjDrawTransform (
+fcos*MirrorSegments, +fsin*length/1000, xoff,
-fsin*MirrorSegments, +fcos*length/1000, yoff,layer
);
}
/* --------------------- Clonk on ladder ---------------------- */
public func OnLadderGrab(clonk, index)
{
// Do some speed when the clonk jumps on the ladder
if(index == 0) return;
particles[index][0][0] += BoundBy(clonk->GetXDir()/2, -25, 25)*Ladder_Precision;
}
public func OnLadderClimb(clonk, index)
{
// The clonk drags on the upper segments and pushes on the lower ones
if(index > 2 && index < ParticleCount-3)
{
particles[index-2][0][0] -= 1*Ladder_Precision/5*(-1+2*clonk->GetDir());
particles[index+2][0][0] += 1*Ladder_Precision/5*(-1+2*clonk->GetDir());
}
else if(index > 2 && index < ParticleCount-2)
{
particles[index-2][0][0] -= 1*Ladder_Precision/5*(-1+2*clonk->GetDir());
particles[index+1][0][0] += 1*Ladder_Precision/5*(-1+2*clonk->GetDir());
}
}
public func GetLadderData(index)
{
var startx = particles[index][0][0]*10;
var starty = particles[index][0][1]*10;
if(index == 0)
{
var angle = Angle(particles[2][0][0], particles[2][0][1], particles[0][0][0], particles[0][0][1]);
return [startx, starty, startx, starty-5000, angle];
}
if(index == ParticleCount-1 || segments[index+1]->CanNotBeClimbed())
{
angle = Angle(particles[index][0][0], particles[index][0][1], particles[index-2][0][0], particles[index-2][0][1]);
}
else
angle = Angle(particles[index+1][0][0], particles[index+1][0][1], particles[index-1][0][0], particles[index-1][0][1]);
var endx = particles[index-1][0][0]*10;
var endy = particles[index-1][0][1]*10;
return [startx, starty, endx, endy, angle];
}
local ActMap = {
Hanging = {
Prototype = Action,
Name = "Hanging"
},
};
local Name = "$Name$";
local Description = "$Description$";
local Collectible = 1;
local Rebuy = true;