1 /**
2 	Pipe
3 
4 	Author: ST-DDT, Marky
5 
6 	The pipe has several states:
7 	- neutral
8 	- source (green line)
9 	- drain (red line)
10 
11 	By default a pipe is neutral, and stays neutral if you connect it to a liquid tank.
12 	- Connected neutral pipes cannot be connected to another (neutral) liquid tank
13 	  (this would not make any sense, or maybe it does if you want to have a liquid
14 	   tank array, but this would lead to all kinds of complications and special
15 	   cases eventually).
16 
17 	However, a pipe can be connected to a liquid transferring structure (pump), too.
18 	- Neutral pipes can be connected to a pump. They then become a drain or source pipe.
19 	- Connected neutral pipes can be connected to a pump. See above.
20 	- Source pipes can be connected to liquid tanks only.
21 	- Drain pipes can be connected to liquid tanks only.
22 
23 	Logic for object use from inventory and interaction menu:
24 	- Both know the using Clonk (interaction menu: the container of the pipe)
25 	- The user may want to connect a drain pipe before connecting a source pipe
26 	- The user may want to connect a neutral pipe
27 	=> separate functions are necessary
28 */
29 
30 static const PIPE_STATE_Neutral = nil;
31 static const PIPE_STATE_Source = "Source";
32 static const PIPE_STATE_Drain = "Drain";
33 static const PIPE_STATE_Air = "Air";
34 
35 local pipe_state = nil;
36 
37 local pipe_line;
38 
39 local ApertureOffsetX = 0;
40 local ApertureOffsetY = 3;
41 
42 /* ---------- Callbacks ---------- */
43 
Hit()44 protected func Hit()
45 {
46 	Sound("Hits::GeneralHit?");
47 }
48 
Destruction()49 private func Destruction()
50 {
51 	// Remove the line first, so that it does not provoke errors on destruction.
52 	// Actually there is an ill-defined state where line contains the pipe and is
53 	// removed. Then line = GetConnectedLine() causes an error, instead use the
54 	// slower find object variant.
55 	var line = FindObject(Find_Func("IsConnectedTo", this));
56 	if (line)
57 		line->RemoveObject();
58 	return;
59 }
60 
61 
IsToolProduct()62 public func IsToolProduct() { return true;}
63 
OnPipeLineRemoval()64 public func OnPipeLineRemoval()
65 {
66 	SetNeutralPipe();
67 	OnPipeLengthChange();
68 	return;
69 }
70 
OnPipeLengthChange()71 public func OnPipeLengthChange()
72 {
73 	// Update usage bar for a possible carrier (the clonk).
74 	var carrier = Contained();
75 	if (carrier)
76 		carrier->~OnInventoryChange();
77 	return;
78 }
79 
80 // Display the line length bar over the pipe icon.
GetInventoryIconOverlay()81 public func GetInventoryIconOverlay()
82 {
83 	var line = GetConnectedLine();
84 	if (!line) return;
85 
86 	var percentage = 100 * line->GetPipeLength() / line.PipeMaxLength;
87 	var red = percentage * 255 / 100;
88 	var green = 255 - red;
89 	// Overlay a usage bar.
90 	var overlay =
91 	{
92 		Bottom = "0.75em",
93 		Margin = ["0.1em", "0.25em"],
94 		BackgroundColor = RGB(0, 0, 0),
95 		margin =
96 		{
97 			Margin = "0.05em",
98 			bar =
99 			{
100 				BackgroundColor = RGB(red, green, 0),
101 				Right = Format("%d%%", percentage),
102 			}
103 		}
104 	};
105 	return overlay;
106 }
107 
CanBeStackedWith(object other)108 public func CanBeStackedWith(object other)
109 {
110 	// Do not stack source/drain/unused pipes
111 	return _inherited(other) && (pipe_state == other.pipe_state);
112 }
113 
114 
115 /**
116  The pump calls this function to prevent clogging of the intake.
117  Cycles through several aperture offset indices.
118  */
HasAperture()119 public func HasAperture() { return true; }
120 
CycleApertureOffset()121 public func CycleApertureOffset()
122 {
123 	// Cycle in three steps of three px each through X and Y
124 	// covering a 3x3 grid on points -3,0,+3
125 	ApertureOffsetX = (ApertureOffsetX + 6) % 9 - 3;
126 	if (!ApertureOffsetX) ApertureOffsetY = (ApertureOffsetY + 6) % 9 - 3;
127 	return true;
128 }
129 
130 /**
131   Container dies: Drop connected pipes so they don't
132   draw huge lines over the landscape
133  */
IsDroppedOnDeath(object clonk)134 func IsDroppedOnDeath(object clonk)
135 {
136 	return !!GetConnectedLine();
137 }
138 
139 
140 /* ---------- Pipe States ---------- */
141 
142 
IsNeutralPipe()143 public func IsNeutralPipe() { return pipe_state == PIPE_STATE_Neutral; }
IsDrainPipe()144 public func IsDrainPipe() { return pipe_state == PIPE_STATE_Drain; }
IsSourcePipe()145 public func IsSourcePipe() { return pipe_state == PIPE_STATE_Source; }
IsAirPipe()146 public func IsAirPipe() { return pipe_state == PIPE_STATE_Air; }
147 
GetPipeState()148 public func GetPipeState() { return pipe_state; }
149 
SetNeutralPipe()150 public func SetNeutralPipe()
151 {
152 	pipe_state = PIPE_STATE_Neutral;
153 
154 	SetGraphics("", nil, GFX_Overlay, GFXOV_MODE_Picture);
155 	Description = "$Description$";
156 	Name = "$Name$";
157 
158 	var line = GetConnectedLine();
159 	if (line)
160 		line->SetNeutral();
161 }
162 
SetDrainPipe()163 public func SetDrainPipe()
164 {
165 	pipe_state = PIPE_STATE_Drain;
166 
167 	SetGraphics("Drain", Pipe, GFX_Overlay, GFXOV_MODE_Picture);
168 	SetObjDrawTransform(1000, 0, 0, 0, 1000, 10000, GFX_Overlay);
169 	Description = "$DescriptionDrain$";
170 	Name = "$NameDrain$";
171 
172 	var line = GetConnectedLine();
173 	if (line)
174 		line->SetDrain();
175 }
176 
SetSourcePipe()177 public func SetSourcePipe()
178 {
179 	pipe_state = PIPE_STATE_Source;
180 
181 	SetGraphics("Source", Pipe, GFX_Overlay, GFXOV_MODE_Picture);
182 	SetObjDrawTransform(1000, 0, 0, 0, 1000, 10000, GFX_Overlay);
183 	Description = "$DescriptionSource$";
184 	Name = "$NameSource$";
185 
186 	var line = GetConnectedLine();
187 	if (line)
188 		line->SetSource();
189 }
190 
SetAirPipe()191 public func SetAirPipe()
192 {
193 	pipe_state = PIPE_STATE_Air;
194 
195 	SetGraphics("Air", Pipe, GFX_Overlay, GFXOV_MODE_Picture);
196 	SetObjDrawTransform(1000, 0, 0, 0, 1000, 10000, GFX_Overlay);
197 	Description = "$DescriptionAir$";
198 	Name = "$NameAir$";
199 
200 	var line = GetConnectedLine();
201 	if (line)
202 		line->SetAir();
203 }
204 
205 /* ---------- Pipe Connection ---------- */
206 
207 
ConnectPipeTo(object target,string specific_pipe_state,bool block_cutting)208 public func ConnectPipeTo(object target, string specific_pipe_state, bool block_cutting)
209 {
210 	if (!target || target->~QueryConnectPipe(this, true))
211 		return false;
212 	AddLineConnectionTo(target, block_cutting);
213 	target->OnPipeConnect(this, specific_pipe_state);
214 	Sound("Objects::Connect");
215 	return true;
216 }
217 
218 /* ---------- Line Connection ---------- */
219 
220 
SetPipeLine(to_line)221 public func SetPipeLine(to_line)
222 {
223 	pipe_line = to_line;
224 }
225 
226 /**
227  Finds a line that is connected to this pipe kit.
228  @return object the pipe, or nil if nothing was found.
229  */
GetConnectedLine()230 public func GetConnectedLine()
231 {
232 	return pipe_line;
233 }
234 
235 
236 /**
237  Connects a line to an object.
238 
239  The pipe kit will connect the line to the target object and itself first.
240  Otherwise, if the pipe kit already has a line, it connects that line to the target.
241 
242  Note: Reports a fatal error if the line would be connected to more than two targets
243        at the same time.
244 
245  @par target the target object
246  */
AddLineConnectionTo(object target,bool block_cutting)247 public func AddLineConnectionTo(object target, bool block_cutting)
248 {
249 	var line = GetConnectedLine();
250 	if (line)
251 	{
252 		if (line->IsConnectedTo(this, true))
253 		{
254 			line->SwitchConnection(this, target);
255 			SetPipeLine(line);
256 			line.BlockPipeCutting = block_cutting;
257 			// Delayed entrance, so that the message is still displayed above the clonk.
258 			ScheduleCall(this, this.Enter, 1, nil, line);
259 			return line;
260 		}
261 		else
262 		{
263 			FatalError("This line is connected to two objects already!");
264 		}
265 	}
266 	else
267 	{
268 		return CreateLine(target, block_cutting);
269 	}
270 }
271 
272 
273 /**
274  Cuts the connection between the line and an object.
275 
276  Note: Reports a fatal error if the target was not
277        connected to the line.
278 
279  @par target the target object
280  */
CutLineConnection(object target)281 public func CutLineConnection(object target)
282 {
283 	var line = GetConnectedLine();
284 	if (!line) return;
285 
286 	// connected only to the kit and a structure
287 	if (line->IsConnectedTo(this, true))
288 	{
289 		target->OnPipeDisconnect(this);
290 		line->RemoveObject();
291 	}
292 	// connected to the target and another structure
293 	else if (line->IsConnectedTo(target, true))
294 	{
295 		target->OnPipeDisconnect(this);
296 		Exit(); // the kit was inside the line at this point.
297 		SetPosition(target->GetX(), target->GetY() + target->GetBottom() - this->GetBottom());
298 		line->SwitchConnection(target, this);
299 		SetPipeLine(line);
300 	}
301 	else
302 	{
303 		FatalError(Format("An object %v is trying to cut the pipe connection, but only objects %v and %v may request a disconnect", target, line->GetActionTarget(0), line->GetActionTarget(1)));
304 	}
305 }
306 
307 // Returns whether the cutting pipe is blocked.
QueryCutLineConnection(object target)308 public func QueryCutLineConnection(object target)
309 {
310 	var line = GetConnectedLine();
311 	if (!line)
312 		return false;
313 	return line.BlockPipeCutting;
314 }
315 
316 /**
317  Creates a new pipe line that is connected to this pipe kit.
318  @par target the target object.
319  @return object the line that was created
320  */
CreateLine(object target,bool block_cutting)321 public func CreateLine(object target, bool block_cutting)
322 {
323 	// Create and connect pipe line.
324 	pipe_line = CreateObject(PipeLine, 0, 0, NO_OWNER);
325 	pipe_line->SetActionTargets(this, target);
326 	pipe_line->SetPipeKit(this);
327 	pipe_line.BlockPipeCutting = block_cutting;
328 	return pipe_line;
329 }
330 
331 
332 /** Will connect liquid line to building at the clonk's position. */
ControlUse(object clonk,int x,int y)333 protected func ControlUse(object clonk, int x, int y)
334 {
335 	var target = FindObject(Find_AtPoint(), Find_Func("CanConnectPipe"));
336 	if (target)
337 	{
338 		ConnectPipeTo(target, GetPipeState());
339 	}
340 	return true;
341 }
342 
343 // Returns to which object the given object is connected through this pipe.
GetConnectedObject(object obj)344 public func GetConnectedObject(object obj)
345 {
346 	if (!pipe_line)
347 		return;
348 	return pipe_line->GetConnectedObject(obj);
349 }
350 
351 /**
352  Displays a message at top-level container of this object.
353  @par message the message
354  */
Report(string message)355 public func Report(string message)
356 {
357 	var reporter = this;
358 	var next = Contained();
359 
360 	while (next)
361 	{
362 		reporter = next;
363 		next = reporter->Contained();
364 	}
365 
366 	reporter->Message(message, ...);
367 }
368 
369 
370 /*-- Saving --*/
371 
SaveScenarioObject(props)372 public func SaveScenarioObject(props)
373 {
374 	if (!inherited(props, ...)) return false;
375 	if (pipe_line) props->AddCall("PipeLine", this, "SetPipeLine", pipe_line);
376 	if (IsNeutralPipe()) props->AddCall("PipeStateNeutral", this, "SetNeutralPipe");
377 	else if (IsDrainPipe()) props->AddCall("PipeStateDrain", this, "SetDrainPipe");
378 	else if (IsSourcePipe()) props->AddCall("PipeStateSource", this, "SetSourcePipe");
379 	else if (IsAirPipe()) props->AddCall("PipeStateAir", this, "SetAirPipe");
380 	return true;
381 }
382 
383 
384 /*-- Properties --*/
385 
386 local Name = "$Name$";
387 local Description = "$Description$";
388 local Collectible = 1;
389 local Components = {Metal = 1};
390