openclonk/planet/Objects.ocd/Structures.ocd/Elevator.ocd/Case.ocd/Script.c

738 lines
16 KiB
C

/*-- Elevator case --*/
#include Library_Structure
#include Library_PowerConsumer
// if you change the vertices in the defcore make sure to adjust this
static const ElevatorCase_additional_vertex_index_begin = 4;
static const ElevatorCase_normal_vertex_index_begin = 0;
static const ElevatorCase_additional_vertex_count = 4;
// Meshes
local front, back;
local elevator;
local partner, partner_was_synced, is_master;
/*-- Callbacks --*/
// Elevator speeds.
private func GetCaseSpeed() { return 20; }
private func GetAutoSpeed() { return GetCaseSpeed() * 2; }
private func GetDrillSpeed() { return GetCaseSpeed() / 2; }
// Case is not a structure, but uses the library.
public func IsStructure() { return false; }
protected func Initialize()
{
AddEffect("CheckAutoMoveTo", this, 1, 30, this);
AddEffect("ElevatorUpperLimitCheck", this, 1, 1, this);
AddEffect("FetchVehicles", this, 1, 10, this);
partner_was_synced = false;
front = CreateObject(Elevator_Case_Front);
back = CreateObjectAbove(Elevator_Case_Back, 0, 13, GetOwner());
front->SetAction("Attach", this);
back->SetAction("Attach", this);
return _inherited(...);
}
// Called by the elevator
func Connect(object connect_to)
{
elevator = connect_to;
SetComDir(COMD_None);
SetAction("DriveIdle");
}
func Destruction()
{
if(partner)
partner->LoseConnection();
if(elevator)
elevator->LostCase();
for(var i = 0; i < 3; ++i)
{
var wood = CreateObjectAbove(Wood, 0, 0, NO_OWNER);
wood->Incinerate();
wood->SetXDir(RandomX(-10, 10));
wood->SetYDir(RandomX(-2, 0));
}
return _inherited(...);
}
func LostElevator()
{
// die x.x
RemoveObject();
}
// Called by the elevator in case a partner elevator was constructed
func StartConnection(object case)
{
partner = case;
partner_was_synced = false;
if(case.partner != this)
{
case->StartConnection(this);
is_master = true;
AddEffect("TryToSync", this, 1, 1, this);
}
else // is slave
{
is_master = false;
MoveTo(nil, 0, case);
}
}
// Called when the other elevator is destroyed or moved
func LoseConnection()
{
partner = nil;
is_master = nil;
partner_was_synced = false;
if(GetEffect("TryToSync", this))
RemoveEffect("TryToSync", this);
SetPartnerVertices(0, 0);
SetActionData(0);
Halt();
}
// slave loses attach to master?
func AttachTargetLost()
{
LoseConnection();
}
public func ExecuteSync()
{
if (!is_master)
FatalError("ExecuteSync() called on slave elevator case!");
partner_was_synced = true;
partner.partner_was_synced = true;
ForceSync();
SetPartnerVertices(partner->GetX() - GetX(), partner->GetY() - GetY());
// Reset power usage.
ResetPowerUsage();
partner->ResetPowerUsage();
// can now attach partner on one of the new vertices
partner->SetAction("Attach", this);
var vertex_data = (ElevatorCase_normal_vertex_index_begin << 8) + ElevatorCase_additional_vertex_index_begin;
partner->SetActionData(vertex_data);
Sound("Click");
}
// sets additional vertices to partner's position
func SetPartnerVertices(int off_x, int off_y)
{
var update_mode = 2; // force immediate update and store information
for(var i = 0; i < ElevatorCase_additional_vertex_count; ++i)
{
SetVertex(ElevatorCase_additional_vertex_index_begin + i, VTX_X, GetVertex(ElevatorCase_normal_vertex_index_begin + i, VTX_X) + off_x, update_mode);
SetVertex(ElevatorCase_additional_vertex_index_begin + i, VTX_Y, GetVertex(ElevatorCase_normal_vertex_index_begin + i, VTX_Y) + off_y, update_mode);
}
}
func IsMaster() { return partner && is_master && partner_was_synced; }
func IsSlave() { return partner && !is_master && partner_was_synced; }
func FxTryToSyncTimer(object target, effect, int time)
{
var diff = Abs(partner->GetY() - GetY());
if(diff > 5) return 1;
ExecuteSync();
return -1;
}
func FxCheckAutoMoveToTimer(object target, effect, int time)
{
if(!elevator) return -1;
if(IsSlave()) return 1;
if(!CheckIdle()) return 1;
if(GetEffect("MoveTo", this)) return 1;
// look for Clonks at shaft
var additional = 20;
var x = GetX() - additional;
var w = GetX() + additional;
var y = elevator->GetY();
var h = LandscapeHeight();
if(IsMaster())
{
x = Min(x, partner->GetX() - additional);
w = Max(w, partner->GetX() + additional);
}
var clonk, best;
for (clonk in FindObjects(Find_InRect(x - GetX(), y - GetY(), w - x, h - y), Find_OCF(OCF_CrewMember), Find_OCF(OCF_Alive), Find_NoContainer(), Find_Allied(GetOwner()), Sort_Distance(), Sort_Reverse()))
{
var proc = clonk.Action.Procedure;
if (clonk->GetComDir() != COMD_Stop && !((proc == "SWIM") && Inside(clonk->GetXDir(), -5, 5)))
continue;
if (proc != "WALK" && proc != "PUSH" && proc != "SCALE" && proc != "HANGLE" && proc != "SWIM") continue;
if (clonk->GetY() < GetY() - 7)
if (!PathFree(GetX(), GetY(), GetX(), clonk->GetY()))
continue;
if (clonk->GetY() > GetY() + 7)
if (!PathFree(GetX(), GetY() + 16, GetX(), clonk->GetY()))
continue;
if ((clonk->GetY() > GetY()) && GetContact(-1, CNAT_Bottom))
continue;
// Do not move to very close Clonks.
if (Abs(GetY() - clonk->GetY()) < 5)
continue;
// Priority rules: Cursor is better than no cursor, nearer is better than farer (Sort_Distance() & Sort_Reverse() do this)
// So unlike in CR's elevator, no distance check has to be done because later cycles are always nearer clonks
if (!best)
best = clonk;
else if (GetCursor(clonk->GetController()) == clonk)
best = clonk;
else if (GetCursor(best->GetController()) != best)
best = clonk;
}
if (best)
MoveTo(best->GetY(), 10, best);
return 1;
}
func FxElevatorUpperLimitCheckTimer(target, effect, time)
{
if (!elevator || IsSlave())
return -1;
var d = GetY() - (elevator->GetY() + 20);
// HOW COULD THIS HAPPEN :C
if (d <= 0)
{
if (GetYDir() < 0)
{
SetPosition(GetX(), GetY() - d);
ForceSync();
ContactTop();
}
else if (GetYDir() == 0)
SetPosition(GetX(), GetY() - d);
effect.Interval = 1;
return 1;
}
// everything okay, adjust timer accordingly
// check less often if far away from elevator
// note: d > 0
var t = BoundBy(d / 3, 1, 20);
effect.Interval = t;
return 1;
}
// for vehicle control
func OutOfRange(object vehicle)
{
if(Abs(GetY() - vehicle->GetY()) > 10) return true;
var min_x = GetX() - 12;
var max_x = GetX() + 12;
if(IsMaster())
{
min_x = Min(min_x, partner->GetX() - 12);
max_x = Max(max_x, partner->GetX() + 12);
}
if(vehicle->GetX() < min_x) return true;
if(vehicle->GetX() > max_x) return true;
return false;
}
protected func FxFetchVehiclesTimer(object target, proplist effect, int time)
{
if (!elevator)
return -1;
if (IsSlave())
return 1;
// look for vehicles
var additional = -5;
var x = GetX() - 12 - additional;
var w = GetX() + 12 + additional;
var y = GetY() - 12;
var h = GetY() + 15;
if (IsMaster())
{
x = Min(x, partner->GetX() - 12 - additional);
w = Max(w, partner->GetX() + 12 + additional);
}
// Fetch vehicles
for (var vehicle in FindObjects(Find_InRect(x - GetX(), y - GetY(), w - x, h - y), Find_Category(C4D_Vehicle), Find_NoContainer(), Find_Func("FitsInElevator")))
{
if (GetEffect("ElevatorControl", vehicle)) continue;
vehicle->SetPosition(vehicle->GetX(), GetY() + 12 - vehicle->GetObjHeight()/2);
vehicle->SetSpeed();
vehicle->SetR();
AddEffect("ElevatorControl", vehicle, 30, 5, vehicle, nil, this);
}
return 1;
}
/*-- Power Consumption --*/
// Keeps track of whether the elevator is currently powered.
local has_power;
private func GetNeededPower()
{
if (partner_was_synced)
return 2 * Elevator_needed_power;
return Elevator_needed_power;
}
// The elevator is the actual power consumer.
public func GetActualPowerConsumer()
{
return elevator;
}
// Elevator has a high priority.
public func GetConsumerPriority() { return 100; }
// Public wrapper for resetting the usage of the slave elevator.
public func ResetPowerUsage()
{
UnregisterPowerRequest();
has_power = false;
return;
}
public func OnNotEnoughPower()
{
has_power = false;
if (GetYDir())
StoreMovementData();
if (GetAction() != "DriveIdle")
Halt(false, true);
return _inherited(...);
}
public func OnEnoughPower()
{
has_power = true;
RestoreMovementData();
return _inherited(...);
}
protected func FxHasPowerStart()
{
return 1;
}
private func StoreMovementData(int y_dir, string action, bool user_requested)
{
if (y_dir == nil)
{
if (GetYDir() < 0)
y_dir = COMD_Up;
if (GetYDir() > 0)
y_dir = COMD_Down;
else
y_dir = COMD_Stop;
}
action = action ?? GetAction();
user_requested = user_requested ?? !CheckIdle();
var effect = GetEffect("StoredMovementData", this);
if (!effect)
effect = AddEffect("StoredMovementData", this, 1, 0, this);
effect.y_dir = y_dir;
effect.action = action;
effect.user_requested = user_requested;
return;
}
private func RestoreMovementData()
{
var effect = GetEffect("StoredMovementData", this);
if (!effect)
return;
var drill = false;
if (effect.action == "Drill")
drill = true;
// This function is only called when enough power is available, so then call
// the movement function with has_power equal to true.
SetMoveDirection(effect.y_dir, effect.user_requested, drill);
RemoveEffect(nil, this, effect);
return;
}
private func SetMoveDirection(int dir, bool user_requested, bool drill)
{
if (IsSlave())
return partner->SetMoveDirection(dir, user_requested, drill, has_power);
// no change?
if (dir == COMD_Up && (GetYDir() < 0)) return;
if (dir == COMD_Down && (GetYDir() > 0)) return;
// already reached top/bottom?
if (GetContact(-1, CNAT_Bottom) && dir == COMD_Down && !drill)
return;
if (GetContact(-1, CNAT_Top) && dir == COMD_Up)
return;
if (dir == COMD_Stop)
return Halt();
var speed = GetCaseSpeed();
// Note: can not move down with full speed because of solidmask problem.
if (!user_requested && dir == COMD_Up)
speed = GetAutoSpeed();
var action = "Drive";
if (drill)
{
action = "Drill";
speed = GetDrillSpeed();
}
if (has_power)
{
if (dir == COMD_Down)
SetYDir(speed);
else if (dir == COMD_Up)
SetYDir(-speed);
SetAction(action);
SetComDir(COMD_None);
ForceSync();
elevator->StartEngine();
}
else
{
StoreMovementData(dir, action, user_requested);
RegisterPowerRequest(GetNeededPower());
}
return;
}
private func Halt(bool user_requested, bool power_out)
{
if (IsSlave())
return;
// Stop the engine if it was still moving.
if (GetYDir())
if(elevator)
elevator->StopEngine();
// Clear speed.
SetAction("DriveIdle");
SetYDir();
ForceSync();
// Unregister the power request and stop automatic movement.
if (user_requested || !power_out)
{
StopAutomaticMovement();
UnregisterPowerRequest();
has_power = false;
}
return;
}
public func ForceSync()
{
if (!IsMaster())
return;
// Clear rounding errors.
SetPosition(GetX(), GetY());
// Adjust partner.
partner->SetPosition(partner->GetX(), GetY());
partner->SetYDir(0);
}
protected func ContactTop()
{
Halt();
Sound("WoodHit*");
return;
}
protected func ContactBottom()
{
// try to dig free
if (GetAction() == "Drill")
{
Drilling();
// wee!
if (!GetContact(-1, CNAT_Bottom))
{
SetYDir(GetDrillSpeed());
return;
}
}
Halt();
Sound("WoodHit*");
return;
}
// Checks whether the elevator should not move because someone's holding it, returns true if idle.
private func CheckIdle()
{
// I have no mind of my own
if (IsSlave())
return false;
var in_rect = Find_InRect(-13, -13, 26, 26);
if (IsMaster())
{
if (partner->GetX() < GetX())
in_rect = Find_InRect(-39, -13, 52, 26);
else
in_rect = Find_InRect(-13, -13, 52, 26);
}
for (var pusher in FindObjects(in_rect, Find_Action("Push")))
{
if (pusher->GetActionTarget() == this)
return false;
if (GetEffect("ElevatorControl", pusher->GetActionTarget()) && GetEffect("ElevatorControl", pusher->GetActionTarget()).case == this)
return false;
if (IsMaster())
{
if (pusher->GetActionTarget() == partner)
return false;
if (GetEffect("ElevatorControl", pusher->GetActionTarget()) && GetEffect("ElevatorControl", pusher->GetActionTarget()).case == partner)
return false;
}
}
return true;
}
private func StopAutomaticMovement()
{
if (GetEffect("MoveTo", this))
{
RemoveEffect("MoveTo", this);
Halt();
}
return;
}
// Moves the case to the specific y-coordinate
// delay in frames, so the elevator does not freak out
// target will be checked again for COMD_Stop and distance after delay run out
public func MoveTo(int y, int delay, object target, bool user_requested)
{
// Not idle?
if (!CheckIdle() && !user_requested)
return;
Halt();
var effect = AddEffect("MoveTo", this, 1, 2, this);
effect.delay = delay;
effect.move_to_y = y;
effect.target = target;
effect.user_requested = user_requested;
return;
}
protected func FxMoveToTimer(object target, proplist effect, int time)
{
if (time < effect.delay) return 1;
// what would take more than 10 seconds?
if ((time - effect.delay) / 36 > 10) return -1;
var y = effect.move_to_y;
if (effect.target)
y = effect.target->GetY();
// Target dead? Don't move and remove effect.
if (y == nil)
{
Halt();
return -1;
}
// Target moves away from elevator shaft, finish movement but stop following
if (effect.target)
if(Abs(GetX() - effect.target->GetX()) > 100)
{
effect.move_to_y = effect.target->GetY();
effect.target = nil;
}
// Destination reached? Stop effect and movement.
if (Abs(GetY() - y) < 5)
{
Halt();
return -1;
}
var dir = COMD_Up;
if (y > GetY())
dir = COMD_Down;
SetMoveDirection(dir, effect.user_requested, false);
return 1;
}
protected func Drilling()
{
var additional_y = 1;
var rect = Rectangle(GetX() - 12, GetY() - 13 - additional_y, GetX() + 12, GetY() + 13 + additional_y);
if (IsMaster())
{
rect.x = Min(rect.x, partner->GetX() - 12);
rect.y = Min(rect.y, partner->GetY() - 13 - additional_y);
rect.w = Max(rect.w, partner->GetX() + 12);
rect.h = Max(rect.h, partner->GetY() + 13 + additional_y);
}
DigFreeRect(rect.x, rect.y, rect.w - rect.x, rect.h - rect.y);
return;
}
/*-- Controls --*/
// Send elevator to the clicked position.
public func ControlUseStart(object clonk, int x, int y)
{
if (IsSlave())
return Control2Master("ControlUseStart", clonk, x, y);
MoveTo(GetY() + y, 0, nil, true);
Sound("Click", nil, nil, clonk->GetOwner());
// Do not trigger a UseStop-callback.
return false;
}
public func ControlDown(object clonk)
{
if (IsSlave())
return Control2Master("ControlDown", clonk);
// Pressing down when already on ground results in drilling.
var drill = !!GetContact(-1, CNAT_Bottom);
StopAutomaticMovement();
SetMoveDirection(COMD_Down, true, drill);
return true;
}
public func ControlUp(object clonk)
{
if (IsSlave())
return Control2Master("ControlUp", clonk);
// what is that player even doing
if (GetY() <= elevator->GetY() + 20)
{
Sound("Click", nil, nil, clonk->GetOwner());
return true;
}
StopAutomaticMovement();
SetMoveDirection(COMD_Up, true, false);
return true;
}
public func ControlStop(object clonk, int control)
{
if (IsSlave())
return Control2Master("ControlStop", clonk, control);
if (control == CON_Up && GetYDir() <= 0)
Halt(true);
else if (control == CON_Down && GetYDir() >= 0)
Halt(true);
return true;
}
public func Control2Master(string call, object clonk)
{
if (!IsSlave())
return false;
return partner->Call(call, clonk, ...);
}
/*-- Scenario saving --*/
func SaveScenarioObject() { return false; }
/*-- Properties --*/
local ActMap = {
Drive = {
Prototype = Action,
Name = "Drive",
Procedure = DFA_FLOAT,
Directions = 1,
X = 0,
Y = 0,
Wdt = 24,
Hgt = 26,
NextAction = "Drive",
},
DriveIdle = {
Prototype = Action,
Name = "DriveIdle",
Procedure = DFA_FLOAT,
Directions = 1,
X = 0,
Y = 0,
Wdt = 24,
Hgt = 26,
NextAction = "DriveIdle",
},
Drill = {
Prototype = Action,
Name = "Drill",
Procedure = DFA_FLOAT,
Directions = 1,
X = 0,
Y = 0,
Wdt = 24,
Hgt = 26,
Delay = 1,
Length = 1,
PhaseCall = "Drilling",
NextAction = "Drill",
Sound = "ElevatorDrilling",
DigFree = 1
},
Attach = {
Prototype = Action,
Name = "Attach",
Procedure = DFA_ATTACH,
Directions = 1,
X = 0,
Y = 0,
Wdt = 24,
Hgt = 26,
NextAction = "Attach"
}
};
local Name = "$Name$";
local Description = "$Description$";
local Touchable = 2;
local HitPoints = 50;
local Plane = 250;