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