forked from Mirrors/openclonk
942 lines
24 KiB
C
942 lines
24 KiB
C
/**
|
|
Pump
|
|
Pumps liquids using drain and source pipes. Features include:
|
|
+ switch on and off
|
|
+ consume/produce a variable amount of power depending on the height of
|
|
source and drain
|
|
|
|
@author Maikel, ST-DDT, Sven2, Newton
|
|
*/
|
|
|
|
#include Library_Structure
|
|
#include Library_Ownable
|
|
#include Library_PowerConsumer
|
|
#include Library_PowerProducer
|
|
#include Library_Tank
|
|
#include Library_ResourceSelection
|
|
|
|
static const PUMP_Menu_Action_Switch_On = "on";
|
|
static const PUMP_Menu_Action_Switch_Off = "off";
|
|
static const PUMP_Menu_Action_Description = "description";
|
|
|
|
|
|
local animation; // animation handle
|
|
|
|
local switched_on; // controlled by Interaction. Indicates whether the user wants to pump or not
|
|
|
|
local powered; // whether the pump has enough power as a consumer, always true if producing
|
|
local power_used; // the amount of power currently consumed or (if negative) produced
|
|
|
|
local clog_count; // increased when the pump doesn't find liquid or can't insert it. When it reaches max_clog_count, it will put the pump into temporary idle mode.
|
|
local max_clog_count = 5; // note that even when max_clog_count is reached, the pump will search through offsets (but in idle mode)
|
|
|
|
local accepted_mat; // currently accepted material.
|
|
|
|
local stored_material_name; //contained liquid
|
|
local stored_material_amount;
|
|
|
|
/** This object is a liquid pump, thus pipes can be connected. */
|
|
public func IsLiquidPump() { return true; }
|
|
public func IsLiquidContainer() { return false; }
|
|
public func IsLiquidTank() { return false; }
|
|
|
|
|
|
// The pump is rather complex for players. If anything happened, tell it to the player via the interaction menu.
|
|
local last_status_message;
|
|
|
|
public func Construction()
|
|
{
|
|
// Rotate at a 45 degree angle towards viewer and add a litte bit of Random
|
|
this.MeshTransformation = Trans_Rotate(50 + RandomX(-10, 10), 0, 1, 0);
|
|
// Default selection
|
|
accepted_mat = nil;
|
|
return _inherited(...);
|
|
}
|
|
|
|
public func IsHammerBuildable() { return true; }
|
|
public func NoConstructionFlip() { return true; }
|
|
|
|
public func Initialize()
|
|
{
|
|
switched_on = true;
|
|
var start = 0;
|
|
var end = GetAnimationLength("pump");
|
|
animation = PlayAnimation("pump", 5, Anim_Linear(GetAnimationPosition(animation), start, end, 35, ANIM_Loop));
|
|
SetState("Wait");
|
|
// Let the fast pump rule know it has been created.
|
|
for (var rule in FindObjects(Find_ID(Rule_FastPump)))
|
|
rule->~OnPumpCreation(this);
|
|
return _inherited(...);
|
|
}
|
|
|
|
|
|
/*-- Interaction --*/
|
|
|
|
public func HasInteractionMenu() { return true; }
|
|
|
|
public func GetPumpControlMenuEntries(object clonk)
|
|
{
|
|
var menu_entries = [];
|
|
// default design of a control menu item
|
|
var custom_entry =
|
|
{
|
|
Right = "100%", Bottom = "2em",
|
|
BackgroundColor = {Std = 0, OnHover = 0x50ff0000},
|
|
image = {Right = "2em"},
|
|
text = {Left = "2em"}
|
|
};
|
|
|
|
// Add info message about what is going on with the pump.
|
|
var status = "$StateOk$";
|
|
var lightbulb_graphics = "Green";
|
|
if (last_status_message != nil)
|
|
{
|
|
status = last_status_message;
|
|
lightbulb_graphics = "Red";
|
|
}
|
|
PushBack(menu_entries, {symbol = this, extra_data = PUMP_Menu_Action_Description,
|
|
custom =
|
|
{
|
|
Prototype = custom_entry,
|
|
Bottom = "1.2em",
|
|
Priority = -1,
|
|
BackgroundColor = RGB(25, 100, 100),
|
|
text = {Prototype = custom_entry.text, Text = status},
|
|
image = {Prototype = custom_entry.image, Symbol = Icon_Lightbulb, GraphicsName = lightbulb_graphics}
|
|
}});
|
|
|
|
// switch on and off
|
|
if (!switched_on)
|
|
PushBack(menu_entries, GetPumpMenuEntry(custom_entry, Icon_Play, "$MsgTurnOn$", 1, PUMP_Menu_Action_Switch_On));
|
|
else
|
|
PushBack(menu_entries, GetPumpMenuEntry(custom_entry, Icon_Stop, "$MsgTurnOff$", 1, PUMP_Menu_Action_Switch_Off));
|
|
|
|
return menu_entries;
|
|
}
|
|
|
|
public func GetPumpMenuEntry(proplist custom_entry, symbol, string text, int priority, extra_data)
|
|
{
|
|
return {symbol = symbol, extra_data = extra_data,
|
|
custom =
|
|
{
|
|
Prototype = custom_entry,
|
|
Priority = priority,
|
|
text = {Prototype = custom_entry.text, Text = text},
|
|
image = {Prototype = custom_entry.image, Symbol = symbol}
|
|
}};
|
|
}
|
|
|
|
public func GetInteractionMenus(object clonk)
|
|
{
|
|
var menus = _inherited(clonk, ...) ?? [];
|
|
var control_menu =
|
|
{
|
|
title = "$Control$",
|
|
entries_callback = this.GetPumpControlMenuEntries,
|
|
callback = "OnPumpControl",
|
|
callback_hover = "OnPumpControlHover",
|
|
callback_target = this,
|
|
BackgroundColor = RGB(0, 50, 50),
|
|
Priority = 20
|
|
};
|
|
PushBack(menus, control_menu);
|
|
var materials_menu =
|
|
{
|
|
title = "$PumpMaterials$",
|
|
entries_callback = this.GetPumpMaterialsMenuEntries,
|
|
callback = "OnPumpMaterials",
|
|
callback_hover = "OnPumpMaterialsHover",
|
|
callback_target = this,
|
|
BackgroundColor = RGB(0, 50, 50),
|
|
Priority = 25
|
|
};
|
|
PushBack(menus, materials_menu);
|
|
return menus;
|
|
}
|
|
|
|
public func OnPumpControlHover(id symbol, string action, desc_menu_target, menu_id)
|
|
{
|
|
var text = "";
|
|
if (action == PUMP_Menu_Action_Switch_On) text = "$DescTurnOn$";
|
|
else if (action == PUMP_Menu_Action_Switch_Off) text = "$DescTurnOff$";
|
|
else if (action == PUMP_Menu_Action_Description) text = this.Description;
|
|
GuiUpdateText(text, menu_id, 1, desc_menu_target);
|
|
}
|
|
|
|
|
|
public func OnPumpControl(symbol_or_object, string action, bool alt)
|
|
{
|
|
if (action == PUMP_Menu_Action_Switch_On || action == PUMP_Menu_Action_Switch_Off)
|
|
ToggleOnOff(true);
|
|
|
|
UpdateInteractionMenus(this.GetPumpControlMenuEntries);
|
|
}
|
|
|
|
private func SetInfoMessage(string msg)
|
|
{
|
|
if (last_status_message == msg) return;
|
|
last_status_message = msg;
|
|
UpdateInteractionMenus(this.GetPumpControlMenuEntries);
|
|
}
|
|
|
|
static const LIBRARY_TANK_Menu_Action_Cut_AirPipe = "cutairpipe";
|
|
static const LIBRARY_TANK_Menu_Action_Add_AirPipe = "addairpipe";
|
|
|
|
// Extend liquid tank connection interface to air pipes.
|
|
public func GetPipeControlMenuEntries(object clonk)
|
|
{
|
|
var menu_entries = inherited(clonk, ...);
|
|
|
|
var drain_pipe = GetDrainPipe();
|
|
// Modify cut drain pipe menu entry for air pipe.
|
|
if (drain_pipe && !drain_pipe->QueryCutLineConnection(this) && drain_pipe->IsAirPipe())
|
|
{
|
|
for (var entry in menu_entries)
|
|
{
|
|
if (entry.extra_data == LIBRARY_TANK_Menu_Action_Cut_Drain)
|
|
{
|
|
entry.extra_data = LIBRARY_TANK_Menu_Action_Cut_AirPipe;
|
|
entry.Priority = 4;
|
|
entry.custom.image.BackgroundColor = RGB(0, 153, 255);
|
|
entry.custom.text.Text = GetConnectedPipeMessage("$MsgCutAirPipe$", GetDrainPipe());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add attach air pipe menu entry.
|
|
var air_pipe = FindAvailablePipe(clonk, Find_Func("IsAirPipe"));
|
|
if (!drain_pipe && air_pipe)
|
|
PushBack(menu_entries, GetTankMenuEntry(air_pipe, GetConnectedPipeMessage("$MsgConnectAirPipe$", air_pipe), 4, LIBRARY_TANK_Menu_Action_Add_AirPipe, RGB(0, 153, 255)));
|
|
return menu_entries;
|
|
}
|
|
|
|
public func OnPipeControlHover(symbol_or_object, string action, desc_menu_target, menu_id)
|
|
{
|
|
if (action == LIBRARY_TANK_Menu_Action_Cut_AirPipe)
|
|
{
|
|
GuiUpdateText(GetConnectedPipeDescription("$DescCutAirPipe$", GetDrainPipe()), menu_id, 1, desc_menu_target);
|
|
return;
|
|
}
|
|
if (action == LIBRARY_TANK_Menu_Action_Add_AirPipe)
|
|
{
|
|
GuiUpdateText(GetConnectedPipeDescription("$DescConnectAirPipe$", symbol_or_object), menu_id, 1, desc_menu_target);
|
|
return;
|
|
}
|
|
return inherited(symbol_or_object, action, desc_menu_target, menu_id, ...);
|
|
}
|
|
|
|
public func OnPipeControl(symbol_or_object, string action, bool alt)
|
|
{
|
|
if (action == LIBRARY_TANK_Menu_Action_Cut_AirPipe)
|
|
{
|
|
this->DoCutPipe(GetDrainPipe());
|
|
UpdateInteractionMenus(this.GetPipeControlMenuEntries);
|
|
return;
|
|
}
|
|
if (action == LIBRARY_TANK_Menu_Action_Add_AirPipe)
|
|
{
|
|
this->DoConnectPipe(symbol_or_object, PIPE_STATE_Air);
|
|
UpdateInteractionMenus(this.GetPipeControlMenuEntries);
|
|
return;
|
|
}
|
|
return inherited(symbol_or_object, action, alt, ...);
|
|
}
|
|
|
|
|
|
/*-- Pipe control --*/
|
|
|
|
public func QueryConnectPipe(object pipe, bool do_msg)
|
|
{
|
|
if (GetDrainPipe() && GetSourcePipe())
|
|
{
|
|
if (do_msg) pipe->Report("$MsgHasPipes$");
|
|
return true;
|
|
}
|
|
else if (pipe->IsSourcePipe() && GetSourcePipe())
|
|
{
|
|
if (do_msg) pipe->Report("$MsgSourcePipeProhibited$");
|
|
return true;
|
|
}
|
|
else if (pipe->IsDrainPipe() && GetDrainPipe())
|
|
{
|
|
if (do_msg) pipe->Report("$MsgDrainPipeProhibited$");
|
|
return true;
|
|
}
|
|
else if (pipe->IsAirPipe() && GetDrainPipe())
|
|
{
|
|
if (do_msg) pipe->Report("$MsgAirPipeProhibited$");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public func OnPipeConnect(object pipe, string specific_pipe_state)
|
|
{
|
|
if (PIPE_STATE_Source == specific_pipe_state)
|
|
{
|
|
SetSourcePipe(pipe);
|
|
pipe->SetSourcePipe();
|
|
pipe->Report("$MsgCreatedSource$");
|
|
}
|
|
else if (PIPE_STATE_Drain == specific_pipe_state)
|
|
{
|
|
SetDrainPipe(pipe);
|
|
pipe->SetDrainPipe();
|
|
pipe->Report("$MsgCreatedDrain$");
|
|
}
|
|
else if (PIPE_STATE_Air == specific_pipe_state)
|
|
{
|
|
// Air pipes take up the place of the drain
|
|
SetDrainPipe(pipe);
|
|
pipe->Report("$MsgCreatedAirDrain$");
|
|
}
|
|
else
|
|
{
|
|
// add a drain if we already connected a source pipe,
|
|
// or if the line is already connected to a container
|
|
var line = pipe->GetConnectedLine();
|
|
var pump_target = !line || line->GetConnectedObject(this);
|
|
if (pump_target) pump_target = pump_target->~IsLiquidContainer();
|
|
if (line->IsAirPipe())
|
|
{
|
|
OnPipeConnect(pipe, PIPE_STATE_Air);
|
|
}
|
|
else if (GetSourcePipe() || pump_target)
|
|
{
|
|
OnPipeConnect(pipe, PIPE_STATE_Drain);
|
|
}
|
|
// otherwise create a source first
|
|
else
|
|
{
|
|
OnPipeConnect(pipe, PIPE_STATE_Source);
|
|
}
|
|
}
|
|
}
|
|
|
|
public func OnPipeDisconnect(object pipe)
|
|
{
|
|
_inherited(pipe, ...);
|
|
// Stop pumping to prevent errors from Pumping() function.
|
|
if (pipe->IsAirPipe())
|
|
CheckState();
|
|
}
|
|
|
|
public func SetSourcePipe(object pipe)
|
|
{
|
|
_inherited(pipe, ...);
|
|
CheckState();
|
|
}
|
|
|
|
public func IsAirPipeConnected()
|
|
{
|
|
if (!GetDrainPipe())
|
|
return false;
|
|
return GetDrainPipe()->~IsAirPipe();
|
|
}
|
|
|
|
/*-- Power stuff --*/
|
|
|
|
public func GetConsumerPriority()
|
|
{
|
|
if (IsAirPipeConnected())
|
|
return 125;
|
|
return 25;
|
|
}
|
|
|
|
public func GetProducerPriority() { return 100; }
|
|
|
|
public func IsSteadyPowerProducer() { return true; }
|
|
|
|
public func OnNotEnoughPower()
|
|
{
|
|
powered = false;
|
|
CheckState();
|
|
return _inherited(...);
|
|
}
|
|
|
|
public func OnEnoughPower()
|
|
{
|
|
powered = true;
|
|
CheckState();
|
|
return _inherited(...);
|
|
}
|
|
|
|
// TODO: these functions may be useful in the liquid tank, maybe move it to that library
|
|
|
|
/** Returns object to which the liquid is pumped */
|
|
private func GetDrainObject()
|
|
{
|
|
var drain = GetDrainPipe();
|
|
if (drain && drain->GetConnectedLine())
|
|
return drain->GetConnectedLine()->GetConnectedObject(this) ?? this;
|
|
return this;
|
|
}
|
|
|
|
/** Returns object from which the liquid is pumped */
|
|
private func GetSourceObject()
|
|
{
|
|
var source = GetSourcePipe();
|
|
if (source && source->GetConnectedLine())
|
|
return source->GetConnectedLine()->GetConnectedObject(this) ?? this;
|
|
return this;
|
|
}
|
|
|
|
/** PhaseCall of Pump: Pump the liquid from the source to the drain pipe */
|
|
protected func Pumping()
|
|
{
|
|
// at this point we can assert that we have power
|
|
|
|
// something went wrong in the meantime?
|
|
// let the central function handle that on next check
|
|
if (!GetSourcePipe() && !IsAirPipeConnected())
|
|
return;
|
|
|
|
// Get the drain object.
|
|
var drain_obj = GetDrainObject();
|
|
|
|
// Don't do anything special if pumping air but inform the drain object.
|
|
if (IsAirPipeConnected())
|
|
{
|
|
if (!GetAirSourceOk() || !GetAirDrainOk())
|
|
return SetState("WaitForLiquid");
|
|
if (drain_obj)
|
|
drain_obj->~OnAirPumped(this);
|
|
return;
|
|
}
|
|
|
|
var pump_ok = true;
|
|
|
|
// is empty? -> try to get liquid
|
|
if (!stored_material_name)
|
|
{
|
|
// get new materials
|
|
var source_obj = GetSourceObject();
|
|
var mat = this->ExtractMaterialFromSource(source_obj, this.PumpSpeed / 10);
|
|
|
|
// no material to pump?
|
|
if (mat)
|
|
{
|
|
stored_material_name = mat[0];
|
|
stored_material_amount = mat[1];
|
|
}
|
|
else
|
|
{
|
|
source_obj->~CycleApertureOffset(this); // try different offsets, so we don't stop pumping just because 1px of earth was dropped on the source pipe
|
|
pump_ok = false;
|
|
}
|
|
}
|
|
if (pump_ok)
|
|
{
|
|
var i = stored_material_amount;
|
|
while (i > 0)
|
|
{
|
|
if (this->InsertMaterialAtDrain(drain_obj, stored_material_name, 1))
|
|
{
|
|
i--;
|
|
}
|
|
// Drain is stuck.
|
|
else
|
|
{
|
|
drain_obj->~CycleApertureOffset(this); // try different offsets, so we don't stop pumping just because 1px of earth was dropped on the drain pipe
|
|
pump_ok = false;
|
|
break;
|
|
}
|
|
}
|
|
stored_material_amount = i;
|
|
if (stored_material_amount <= 0)
|
|
stored_material_name = nil;
|
|
}
|
|
|
|
if (pump_ok)
|
|
{
|
|
clog_count = 0;
|
|
}
|
|
else
|
|
{
|
|
// Put into wait state if no liquid could be pumped for a while or if the drain has no aperture, i.e. is a liquid tank.
|
|
if (++clog_count >= max_clog_count || !drain_obj->~HasAperture())
|
|
{
|
|
SetState("WaitForLiquid");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
// interface for the extraction logic
|
|
func ExtractMaterialFromSource(object source_obj, int amount)
|
|
{
|
|
if (source_obj->~IsLiquidContainer())
|
|
{
|
|
return source_obj->RemoveLiquid(accepted_mat, amount, this);
|
|
}
|
|
else
|
|
{
|
|
var mat = source_obj->ExtractLiquidAmount(source_obj.ApertureOffsetX, source_obj.ApertureOffsetY, amount, true);
|
|
if (mat)
|
|
return [MaterialName(mat[0]), mat[1]];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// interface for the insertion logic
|
|
public func InsertMaterialAtDrain(object drain_obj, string material_name, int amount)
|
|
{
|
|
// insert material into containers, if possible
|
|
if (drain_obj->~IsLiquidContainer())
|
|
{
|
|
amount -= drain_obj->PutLiquid(material_name, amount, this);
|
|
}
|
|
else
|
|
{
|
|
// convert to actual material, and insert remaining
|
|
var material_index = Material(material_name);
|
|
if (material_index == -1 && material_name != nil)
|
|
material_index = Material(GetDefinition(material_name)->GetLiquidMaterial());
|
|
if (material_index != -1)
|
|
{
|
|
while (amount > 0 && drain_obj->InsertMaterial(material_index, drain_obj.ApertureOffsetX, drain_obj.ApertureOffsetY))
|
|
amount--;
|
|
}
|
|
}
|
|
return amount <= 0;
|
|
}
|
|
|
|
|
|
/** Re check state and change the state if needed */
|
|
public func CheckState()
|
|
{
|
|
var is_fullcon = GetCon() >= 100;
|
|
// The pump can work without source if it needs to supply air.
|
|
var can_pump = (GetSourcePipe() || IsAirPipeConnected()) && is_fullcon && switched_on;
|
|
|
|
// Can't pump at all -> wait.
|
|
if (!can_pump)
|
|
{
|
|
if (!GetSourcePipe() && switched_on)
|
|
SetInfoMessage("$StateNoSource$");
|
|
SetState("Wait");
|
|
}
|
|
else if(IsAirPipeConnected())
|
|
{
|
|
if (!GetAirSourceOk())
|
|
{
|
|
SetInfoMessage("$StateNoAir$");
|
|
SetState("WaitForLiquid");
|
|
}
|
|
else if (!GetAirDrainOk())
|
|
{
|
|
SetInfoMessage("$StateNoAirNeed$");
|
|
SetState("WaitForLiquid");
|
|
}
|
|
else
|
|
{
|
|
// Can pump, has air but has no power -> wait for power.
|
|
if (!powered)
|
|
{
|
|
SetInfoMessage("$StateNoPower$");
|
|
SetState("WaitForPower");
|
|
}
|
|
else
|
|
{
|
|
SetInfoMessage();
|
|
clog_count = 0;
|
|
SetState("Pump");
|
|
}
|
|
UpdatePowerUsage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Can pump but has no liquid or can't dispense liquid -> wait.
|
|
var source_mat = GetLiquidSourceMaterial();
|
|
var source_ok = IsInResourceSelection(source_mat);
|
|
var drain_ok = GetLiquidDrainOk(source_mat);
|
|
if (!source_ok || !drain_ok)
|
|
{
|
|
accepted_mat = nil;
|
|
if (!source_ok)
|
|
SetInfoMessage("$StateNoInput$");
|
|
else if (!drain_ok)
|
|
SetInfoMessage("$StateNoOutput$");
|
|
SetState("WaitForLiquid");
|
|
}
|
|
else
|
|
{
|
|
accepted_mat = source_mat;
|
|
// can pump, has liquid but has no power -> wait for power
|
|
if (!powered)
|
|
{
|
|
SetInfoMessage("$StateNoPower$");
|
|
SetState("WaitForPower");
|
|
}
|
|
// otherwise, pump! :-)
|
|
else
|
|
{
|
|
SetInfoMessage();
|
|
clog_count = 0;
|
|
SetState("Pump");
|
|
}
|
|
|
|
// regularly update the power usage while pumping or waiting for power
|
|
UpdatePowerUsage();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Get current height the pump has to push liquids upwards (input.y - output.y) */
|
|
private func GetPumpHeight()
|
|
{
|
|
// compare each the surfaces of the bodies of liquid pumped
|
|
|
|
// find Y position of surface of liquid that is pumped to target
|
|
var source_obj = GetSourceObject();
|
|
var source_x = source_obj.ApertureOffsetX;
|
|
var source_y = source_obj.ApertureOffsetY;
|
|
if (source_obj->GBackLiquid(source_x, source_y))
|
|
{
|
|
var src_mat = source_obj->GetMaterial(source_x, source_y);
|
|
while (src_mat == source_obj->GetMaterial(source_x, source_y - 1))
|
|
--source_y;
|
|
}
|
|
// same for target (use same function as if inserting)
|
|
var target_pos = {X = 0, Y = 0};
|
|
var drain_obj = GetDrainObject();
|
|
drain_obj->CanInsertMaterial(Material("Water"), drain_obj.ApertureOffsetX, drain_obj.ApertureOffsetY, target_pos);
|
|
return source_obj->GetY() + source_y - target_pos.Y;
|
|
}
|
|
|
|
/** Recheck power usage/production for current pump height
|
|
and make the pump a producer / consumer for the power system */
|
|
private func UpdatePowerUsage()
|
|
{
|
|
var new_power;
|
|
if (IsUsingPower())
|
|
new_power = PumpHeight2Power(GetPumpHeight());
|
|
else
|
|
new_power = 0;
|
|
|
|
// do nothing if not necessary
|
|
if (new_power == power_used)
|
|
{
|
|
// But still set powered to true if power_used was not positive.
|
|
if (power_used <= 0)
|
|
powered = true;
|
|
return;
|
|
}
|
|
|
|
// and update energy system
|
|
if (new_power > 0)
|
|
{
|
|
if (power_used < 0)
|
|
{
|
|
powered = false; // needed since the flag was set manually
|
|
UnregisterPowerProduction();
|
|
}
|
|
RegisterPowerRequest(new_power);
|
|
}
|
|
else if (new_power < 0)
|
|
{
|
|
if (power_used > 0)
|
|
UnregisterPowerRequest();
|
|
RegisterPowerProduction(-new_power);
|
|
powered = true; // when producing, we always have power
|
|
}
|
|
else // new_power == 0
|
|
{
|
|
if (power_used < 0)
|
|
UnregisterPowerProduction();
|
|
else if (power_used > 0)
|
|
UnregisterPowerRequest();
|
|
powered = true;
|
|
}
|
|
|
|
power_used = new_power;
|
|
return;
|
|
}
|
|
|
|
// Return whether the pump should be using power in the current state.
|
|
private func IsUsingPower()
|
|
{
|
|
return switched_on && (GetAction() == "Pump" || GetAction() == "WaitForPower");
|
|
}
|
|
|
|
// Transform pump height (input.y - output.y) into required power.
|
|
private func PumpHeight2Power(int pump_height)
|
|
{
|
|
// Pumping upwards will always cost the minimum energy.
|
|
// Pumping downwards will only produce energy after an offset.
|
|
var power_offset = 10;
|
|
// Max power consumed / produced.
|
|
var max_power = 60;
|
|
// Calculate the used power in steps of ten, every 60 pixels represents ten units.
|
|
var used_power = pump_height / 60 * 10;
|
|
// If the pump height is positive then add the minimum energy.
|
|
if (pump_height >= 0)
|
|
used_power = Min(used_power + 10, max_power);
|
|
// Pumping power downwards never costs energy, but only brings something if offset is overcome.
|
|
else
|
|
used_power = BoundBy(used_power + power_offset - 10, -max_power, 0);
|
|
// Pumped air always requires 20 power.
|
|
if (IsAirPipeConnected())
|
|
used_power = 20;
|
|
return used_power;
|
|
}
|
|
|
|
// Returns whether there is liquid at the source pipe to pump.
|
|
private func GetLiquidSourceMaterial()
|
|
{
|
|
// Get the source object and check whether there is liquid.
|
|
var source_obj = GetSourceObject();
|
|
if (!source_obj)
|
|
return;
|
|
// The source is a liquid container: check which material will be supplied.
|
|
if (source_obj->~IsLiquidContainer())
|
|
{
|
|
var liquid = source_obj->HasLiquid(GetResourceSelection());
|
|
if (liquid)
|
|
return liquid->GetLiquidType();
|
|
return;
|
|
}
|
|
var is_liquid = source_obj->GBackLiquid(source_obj.ApertureOffsetX, source_obj.ApertureOffsetY);
|
|
var liquid = MaterialName(source_obj->GetMaterial(source_obj.ApertureOffsetX, source_obj.ApertureOffsetY));
|
|
if (!is_liquid)
|
|
{
|
|
// Try different offsets, so we can resume pumping after clog because 1px of earth was dropped on the source pipe.
|
|
source_obj->~CycleApertureOffset(this);
|
|
return;
|
|
}
|
|
return liquid;
|
|
}
|
|
|
|
// Returns whether the drain pipe is free or the liquid container accepts the given material.
|
|
private func GetLiquidDrainOk(string liquid)
|
|
{
|
|
if (!liquid)
|
|
return false;
|
|
var drain_obj = GetDrainObject();
|
|
if (drain_obj->~HasAperture())
|
|
{
|
|
var material_index = Material(liquid);
|
|
if (material_index == -1 && liquid != nil)
|
|
material_index = Material(GetDefinition(liquid)->GetLiquidMaterial());
|
|
if (!drain_obj->CanInsertMaterial(material_index, drain_obj.ApertureOffsetX, drain_obj.ApertureOffsetY))
|
|
{
|
|
drain_obj->~CycleApertureOffset(this); // try different offsets, so we can resume pumping after clog because 1px of earth was dropped on the source pipe
|
|
return false;
|
|
}
|
|
}
|
|
else if (drain_obj->~IsLiquidContainer())
|
|
{
|
|
if (!drain_obj->AcceptsLiquid(liquid, 1))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns whether the source (or alternatively the pump itself) is in free air.
|
|
public func GetAirSourceOk()
|
|
{
|
|
var source_obj = GetSourceObject();
|
|
if (!source_obj)
|
|
return !GBackSemiSolid();
|
|
var is_air = !source_obj->GBackSemiSolid(source_obj.ApertureOffsetX, source_obj.ApertureOffsetY);
|
|
if (!is_air)
|
|
source_obj->~CycleApertureOffset(this);
|
|
return is_air;
|
|
}
|
|
|
|
// Returns whether the other side of air drain is in need of air.
|
|
public func GetAirDrainOk()
|
|
{
|
|
var drain_obj = GetDrainObject();
|
|
if (!drain_obj)
|
|
return false;
|
|
return drain_obj->~QueryAirNeed(this);
|
|
}
|
|
|
|
// Set the state of the pump, retaining the animation position and updating the power usage.
|
|
private func SetState(string act)
|
|
{
|
|
if (act == GetAction())
|
|
return;
|
|
|
|
// Set animation depending on the current action.
|
|
var start = 0;
|
|
var end = GetAnimationLength("pump");
|
|
var anim_pos = GetAnimationPosition(animation);
|
|
if (act == "Pump")
|
|
{
|
|
SetAnimationPosition(animation, Anim_Linear(anim_pos, start, end, 35, ANIM_Loop));
|
|
}
|
|
else if(act == "WaitForLiquid")
|
|
{
|
|
SetAnimationPosition(animation, Anim_Linear(anim_pos, start, end, 350, ANIM_Loop));
|
|
}
|
|
else
|
|
{
|
|
SetAnimationPosition(animation, Anim_Const(anim_pos));
|
|
}
|
|
|
|
// Deactivate power usage when not pumping.
|
|
if (powered && (act == "Wait" || act == "WaitForLiquid"))
|
|
{
|
|
if (power_used < 0)
|
|
UnregisterPowerProduction();
|
|
else if (power_used > 0)
|
|
UnregisterPowerRequest();
|
|
|
|
power_used = 0;
|
|
powered = false;
|
|
}
|
|
// Finally, set the action.
|
|
SetAction(act);
|
|
}
|
|
|
|
/* Deactivates a running pump or vice-versa. */
|
|
func ToggleOnOff(bool no_menu_refresh)
|
|
{
|
|
switched_on = !switched_on;
|
|
CheckState();
|
|
if (!switched_on)
|
|
SetInfoMessage("$StateTurnedOff$");
|
|
if (!no_menu_refresh)
|
|
UpdateInteractionMenus(this.GetPumpControlMenuEntries);
|
|
}
|
|
|
|
|
|
/*-- Material Selection --*/
|
|
|
|
func IsResourceSelectionParent(id child_resource, id parent_resource)
|
|
{
|
|
return child_resource->~GetParentLiquidType() == parent_resource;
|
|
}
|
|
|
|
|
|
func ShowResourceSelectionMenuEntry(id resource)
|
|
{
|
|
return resource->~IsLiquid() && resource != Library_Liquid;
|
|
}
|
|
|
|
|
|
func InitResourceSelection()
|
|
{
|
|
// Add all liquids to the list of ones allowed to pump.
|
|
var index = 0, resource;
|
|
while (resource = GetDefinition(index++))
|
|
if (ShowResourceSelectionMenuEntry(resource))
|
|
AddToResourceSelection(resource);
|
|
return inherited();
|
|
}
|
|
|
|
func IsInResourceSelection(/* any */ mat)
|
|
{
|
|
if (GetType(mat) == C4V_Def)
|
|
{
|
|
return inherited(mat);
|
|
}
|
|
for (var resource in GetResourceSelection())
|
|
{
|
|
if (resource->GetLiquidType() == mat)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public func GetPumpMaterialsMenuEntries(object clonk)
|
|
{
|
|
return GetResourceSelectionMenuEntries(clonk);
|
|
}
|
|
|
|
public func OnPumpMaterialsHover(id symbol, string action, desc_menu_target, menu_id)
|
|
{
|
|
var text = "";
|
|
if (action == RESOURCE_SELECT_Menu_Action_Resource_Enable)
|
|
{
|
|
text = Format("$MsgEnableMaterial$", symbol->GetName());
|
|
}
|
|
else if (action == RESOURCE_SELECT_Menu_Action_Resource_Disable)
|
|
{
|
|
text = Format("$MsgDisableMaterial$", symbol->GetName());
|
|
}
|
|
GuiUpdateText(text, menu_id, 1, desc_menu_target);
|
|
}
|
|
|
|
public func OnPumpMaterials(symbol_or_object, string action, bool alt)
|
|
{
|
|
if (action == RESOURCE_SELECT_Menu_Action_Resource_Enable)
|
|
{
|
|
AddToResourceSelection(symbol_or_object);
|
|
}
|
|
else if (action == RESOURCE_SELECT_Menu_Action_Resource_Disable)
|
|
{
|
|
RemoveFromResourceSelection(symbol_or_object);
|
|
}
|
|
UpdateInteractionMenus(this.GetPumpMaterialsMenuEntries);
|
|
}
|
|
|
|
|
|
/*-- Properties --*/
|
|
|
|
protected func Definition(def)
|
|
{
|
|
// for title image
|
|
SetProperty("PictureTransformation", Trans_Rotate(50, 0, 1, 0), def);
|
|
// for building preview
|
|
SetProperty("MeshTransformation", Trans_Rotate(50, 0, 1, 0), def);
|
|
return _inherited(def, ...);
|
|
}
|
|
|
|
/*
|
|
States
|
|
"Wait": turned off or source pipe not connected
|
|
"WaitForPower": turned on but no power (does consume power)
|
|
"WaitForLiquid": turned on but no liquid (does not consume power)
|
|
"Pump": currently working and consuming/producing power
|
|
*/
|
|
local ActMap = {
|
|
Pump = {
|
|
Prototype = Action,
|
|
Name = "Pump",
|
|
Length = 30,
|
|
Delay = 3,
|
|
Sound = "Structures::Pumpjack",
|
|
NextAction = "Pump",
|
|
StartCall = "CheckState",
|
|
PhaseCall = "Pumping"
|
|
},
|
|
Wait = {
|
|
Prototype = Action,
|
|
Name = "Wait",
|
|
Delay = 90,
|
|
NextAction = "Wait",
|
|
EndCall = "CheckState"
|
|
},
|
|
WaitForPower = {
|
|
Prototype = Action,
|
|
Name = "WaitForPower",
|
|
Delay = 30,
|
|
NextAction = "WaitForPower",
|
|
EndCall = "CheckState"
|
|
},
|
|
WaitForLiquid = {
|
|
Prototype = Action,
|
|
Name = "WaitForLiquid",
|
|
Delay = 30,
|
|
NextAction = "WaitForLiquid",
|
|
EndCall = "CheckState"
|
|
}
|
|
};
|
|
|
|
local Name = "$Name$";
|
|
local Description = "$Description$";
|
|
local BlastIncinerate = 70;
|
|
local NoBurnDecay = true;
|
|
local HitPoints = 70;
|
|
local FireproofContainer = true;
|
|
local Components = {Wood = 1, Metal = 3};
|
|
// Pump speed in amount of pixels to pump per 30 frames.
|
|
local PumpSpeed = 50;
|