forked from Mirrors/openclonk
738 lines
16 KiB
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;
|