1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* The command stack controls an object's complex and independent behavior */
18
19#include <C4Include.h>
20#include <C4Command.h>
21
22#include <C4Object.h>
23#include <C4ObjectCom.h>
24#include <C4ObjectInfo.h>
25#include <C4Random.h>
26#include <C4Game.h>
27#include <C4ObjectMenu.h>
28#include <C4Player.h>
29#include <C4Wrappers.h>
30
31const int32_t MoveToRange = 5, LetGoRange1 = 7, LetGoRange2 = 30, DigRange = 1;
32const int32_t FollowRange = 6, PushToRange = 10, DigOutPositionRange = 15;
33const int32_t PathRange = 20, MaxPathRange = 1000;
34const int32_t JumpAngle = 35, JumpLowAngle = 80, JumpAngleRange = 10, JumpHighAngle = 0;
35const int32_t FlightAngleRange = 60;
36const int32_t LetGoHangleAngle = 110;
37
38StdEnumAdapt<int32_t>::Entry EnumAdaptCommandEntries[C4CMD_Last - C4CMD_First + 2];
39
40const char *CommandName(int32_t iCommand)
41{
42 static const char *szCommandName[] =
43 {
44 "None", "Follow", "MoveTo", "Enter", "Exit", "Grab", "Build", "Throw", "Chop",
45 "UnGrab", "Jump", "Wait", "Get", "Put", "Drop", "Dig", "Activate", "PushTo",
46 "Construct", "Transfer", "Attack", "Context", "Buy", "Sell", "Acquire",
47 "Energy", "Retry", "Home", "Call", "Take", "Take2"
48 };
49
50 if (!Inside<int32_t>(ival: iCommand, lbound: C4CMD_First, rbound: C4CMD_Last)) return "None";
51
52 return szCommandName[iCommand];
53}
54
55const char *LoadCommandNameResStr(const std::int32_t command)
56{
57 static constexpr C4ResStrTableKeyFormat<> CommandNameIds[]
58 {
59 C4ResStrTableKey::IDS_COMM_NONE, C4ResStrTableKey::IDS_COMM_FOLLOW, C4ResStrTableKey::IDS_COMM_MOVETO, C4ResStrTableKey::IDS_COMM_ENTER,
60 C4ResStrTableKey::IDS_COMM_EXIT, C4ResStrTableKey::IDS_COMM_GRAB, C4ResStrTableKey::IDS_COMM_BUILD, C4ResStrTableKey::IDS_COMM_THROW, C4ResStrTableKey::IDS_COMM_CHOP,
61 C4ResStrTableKey::IDS_COMM_UNGRAB, C4ResStrTableKey::IDS_COMM_JUMP, C4ResStrTableKey::IDS_COMM_WAIT, C4ResStrTableKey::IDS_COMM_GET, C4ResStrTableKey::IDS_COMM_PUT,
62 C4ResStrTableKey::IDS_COMM_DROP, C4ResStrTableKey::IDS_COMM_DIG, C4ResStrTableKey::IDS_COMM_ACTIVATE, C4ResStrTableKey::IDS_COMM_PUSHTO,
63 C4ResStrTableKey::IDS_COMM_CONSTRUCT, C4ResStrTableKey::IDS_COMM_TRANSFER, C4ResStrTableKey::IDS_COMM_ATTACK, C4ResStrTableKey::IDS_COMM_CONTEXT,
64 C4ResStrTableKey::IDS_COMM_BUY, C4ResStrTableKey::IDS_COMM_SELL, C4ResStrTableKey::IDS_COMM_ACQUIRE, C4ResStrTableKey::IDS_COMM_ENERGY, C4ResStrTableKey::IDS_COMM_RETRY,
65 C4ResStrTableKey::IDS_CON_HOME, C4ResStrTableKey::IDS_COMM_CALL, C4ResStrTableKey::IDS_COMM_TAKE, C4ResStrTableKey::IDS_COMM_TAKE2
66 };
67
68 if (!Inside<int32_t>(ival: command, lbound: C4CMD_First, rbound: C4CMD_Last)) return LoadResStr(id: C4ResStrTableKey::IDS_COMM_NONE);
69
70 return LoadResStr(id: CommandNameIds[command]);
71}
72
73bool InitEnumAdaptCommandEntries()
74{
75 for (int32_t i = C4CMD_First; i <= C4CMD_Last; i++)
76 {
77 EnumAdaptCommandEntries[i - C4CMD_First].Name = CommandName(iCommand: i);
78 EnumAdaptCommandEntries[i - C4CMD_First].Val = i;
79 }
80 EnumAdaptCommandEntries[C4CMD_Last - C4CMD_First + 1].Name = nullptr;
81 return true;
82}
83
84const bool InitEnumAdaptCommandEntriesDummy = InitEnumAdaptCommandEntries();
85
86int32_t CommandByName(const char *szCommand)
87{
88 for (int32_t cnt = C4CMD_First; cnt <= C4CMD_Last; cnt++)
89 if (SEqual(szStr1: szCommand, szStr2: CommandName(iCommand: cnt)))
90 return cnt;
91 return C4CMD_None;
92}
93
94void AdjustMoveToTarget(int32_t &rX, int32_t &rY, bool fFreeMove, int32_t iShapeHgt)
95{
96 // Above solid (always)
97 int32_t iY;
98 for (iY = rY; (iY >= 0) && GBackSolid(x: rX, y: iY); iY--);
99 if (iY >= 0) rY = iY;
100 // No-free-move adjustments (i.e. if walking)
101 if (!fFreeMove)
102 {
103 // Drop down to bottom of free space
104 if (!GBackSemiSolid(x: rX, y: rY))
105 {
106 for (iY = rY; (iY < GBackHgt) && !GBackSemiSolid(x: rX, y: iY + 1); iY++);
107 if (iY < GBackHgt) rY = iY;
108 }
109 // Vertical shape offset above solid
110 if (GBackSolid(x: rX, y: rY + 1) || GBackSolid(x: rX, y: rY + 5))
111 if (!GBackSemiSolid(x: rX, y: rY - iShapeHgt / 2))
112 rY -= iShapeHgt / 2;
113 }
114}
115
116bool FreeMoveTo(C4Object *cObj)
117{
118 // Floating: we accept any move-to target
119 if (cObj->GetProcedure() == DFA_FLOAT) return true;
120 // Can fly: we accept any move-to target
121 if (cObj->GetPhysical()->CanFly) return true;
122 // Assume we're walking: move-to targets are adjusted
123 return false;
124}
125
126bool AdjustSolidOffset(int32_t &rX, int32_t &rY, int32_t iXOff, int32_t iYOff)
127{
128 // In solid: fail
129 if (GBackSolid(x: rX, y: rY)) return false;
130 // Y Offset
131 int32_t cnt;
132 for (cnt = 1; cnt < iYOff; cnt++)
133 {
134 if (GBackSolid(x: rX, y: rY + cnt) && !GBackSolid(x: rX, y: rY - cnt)) rY--;
135 if (GBackSolid(x: rX, y: rY - cnt) && !GBackSolid(x: rX, y: rY + cnt)) rY++;
136 }
137 // X Offset
138 for (cnt = 1; cnt < iXOff; cnt++)
139 {
140 if (GBackSolid(x: rX + cnt, y: rY) && !GBackSolid(x: rX - cnt, y: rY)) rX--;
141 if (GBackSolid(x: rX - cnt, y: rY) && !GBackSolid(x: rX + cnt, y: rY)) rX++;
142 }
143 // Done
144 return true;
145}
146
147int32_t SolidOnWhichSide(int32_t iX, int32_t iY)
148{
149 for (int32_t cx = 1; cx < 10; cx++)
150 for (int32_t cy = 0; cy < 10; cy++)
151 {
152 if (GBackSolid(x: iX - cx, y: iY - cy) || GBackSolid(x: iX - cx, y: iY + cy)) return -1;
153 if (GBackSolid(x: iX + cx, y: iY - cy) || GBackSolid(x: iX + cx, y: iY + cy)) return +1;
154 }
155 return 0;
156}
157
158C4Command::C4Command()
159{
160 Default();
161}
162
163C4Command::~C4Command()
164{
165 Clear();
166}
167
168void C4Command::Default()
169{
170 Command = C4CMD_None;
171 cObj = nullptr;
172 Evaluated = false;
173 PathChecked = false;
174 Finished = false;
175 Tx = C4VNull;
176 Ty = 0;
177 Target = Target2 = nullptr;
178 Data = 0;
179 UpdateInterval = 0;
180 Failures = 0;
181 Retries = 0;
182 Permit = 0;
183 Text.clear();
184 Next = nullptr;
185 iExec = 0;
186 BaseMode = C4CMD_Mode_SilentSub;
187}
188
189static bool ObjectAddWaypoint(int32_t iX, int32_t iY, intptr_t iTransferTarget, intptr_t ipObject)
190{
191 C4Object *cObj = reinterpret_cast<C4Object *>(ipObject); if (!cObj) return false;
192
193 // Transfer waypoint
194 if (iTransferTarget)
195 return cObj->AddCommand(iCommand: C4CMD_Transfer, pTarget: reinterpret_cast<C4Object *>(iTransferTarget), iTx: iX, iTy: iY, iUpdateInterval: 0, pTarget2: nullptr, fInitEvaluation: false);
196
197 // Solid offset
198 AdjustSolidOffset(rX&: iX, rY&: iY, iXOff: cObj->Shape.Wdt / 2, iYOff: cObj->Shape.Hgt / 2);
199
200 // Standard movement waypoint update interval
201 int32_t iUpdate = 25;
202 // Waypoints before transfer zones are not updated (enforce move to that waypoint)
203 if (cObj->Command && (cObj->Command->Command == C4CMD_Transfer)) iUpdate = 0;
204 // Add waypoint
205 assert(cObj->Command);
206 if (!cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: iX, iTy: iY, iUpdateInterval: 25, pTarget2: nullptr, fInitEvaluation: false, iData: cObj->Command->Data)) return false;
207
208 return true;
209}
210
211void C4Command::MoveTo()
212{
213 // Determine move-to range
214 int32_t iMoveToRange = MoveToRange;
215 if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
216
217 // Current object position
218 int32_t cx, cy; cx = cObj->x; cy = cObj->y;
219 bool fWaypoint = false;
220 if (Next && (Next->Command == C4CMD_MoveTo)) fWaypoint = true;
221
222 // Contained: exit
223 if (cObj->Contained)
224 {
225 cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
226 }
227
228 // Check path (crew members or specific only)
229 if ((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)
230 if (!PathChecked)
231 // Not too far
232 if (Distance(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty) < MaxPathRange)
233 // Not too close
234 if (!(Inside<C4ValueInt>(ival: cx - Tx._getInt(), lbound: -PathRange, rbound: +PathRange) && Inside<C4ValueInt>(ival: cy - Ty, lbound: -PathRange, rbound: +PathRange)))
235 {
236 // Path not free: find path
237 if (!PathFree(x1: cx, y1: cy, x2: Tx._getInt(), y2: Ty))
238 {
239 Game.PathFinder.EnableTransferZones(fEnabled: !cObj->Def->NoTransferZones);
240 Game.PathFinder.SetLevel(cObj->Def->Pathfinder);
241 if (!Game.PathFinder.Find(iFromX: cObj->x, iFromY: cObj->y,
242 iToX: Tx._getInt(), iToY: Ty,
243 fnSetWaypoint: &ObjectAddWaypoint,
244 iWaypointParameter: reinterpret_cast<intptr_t>(cObj))) // intptr for 64bit?
245 {
246 /* Path not found: react? */ PathChecked = true; /* recheck delay */
247 }
248 return;
249 }
250 // Path free: recheck delay
251 else
252 PathChecked = true;
253 }
254 // Path recheck
255 if (!Tick35) PathChecked = false;
256
257 // Pushing grab only or not desired: let go (pulling, too?)
258 if (cObj->GetProcedure() == DFA_PUSH)
259 if (cObj->Action.Target)
260 if (cObj->Action.Target->Def->Grab == 2 || !(Data & C4CMD_MoveTo_PushTarget))
261 {
262 // Re-evaluate this command because vehicle control might have blocked evaluation
263 Evaluated = false;
264 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
265 }
266
267 // Special by procedure
268 switch (cObj->GetProcedure())
269 {
270 // Push/pull
271 case DFA_PUSH: case DFA_PULL:
272 // Use target object's position if on final waypoint
273 if (!fWaypoint)
274 if (cObj->Action.Target)
275 {
276 cx = cObj->Action.Target->x; cy = cObj->Action.Target->y;
277 }
278 break;
279 // Chop, build, dig, bridge: stop
280 case DFA_CHOP: case DFA_BUILD: case DFA_DIG: case DFA_BRIDGE:
281 ObjectComStop(cObj);
282 break;
283 }
284
285 // Target range
286 int32_t iTargetRange = iMoveToRange;
287 int32_t iRangeFactorTop = 1, iRangeFactorBottom = 1, iRangeFactorSide = 1;
288
289 // Crew members/pathfinder specific target range
290 if (cObj->OCF & OCF_CrewMember) // || cObj->Def->Pathfinder ? (Sven2)
291 {
292 // Range by size
293 iTargetRange = cObj->Shape.Wdt / 5;
294 // Easier range if waypoint
295 if (fWaypoint)
296 if (cObj->GetProcedure() != DFA_SCALE)
297 {
298 iRangeFactorTop = 3; iRangeFactorSide = 3; iRangeFactorBottom = 2;
299 }
300 }
301
302 // Target reached (success)
303 if (Inside<C4ValueInt>(ival: cx - Tx._getInt(), lbound: -iRangeFactorSide * iTargetRange, rbound: +iRangeFactorSide * iTargetRange)
304 && Inside(ival: cy - Ty, lbound: -iRangeFactorBottom * iTargetRange, rbound: +iRangeFactorTop * iTargetRange))
305 {
306 cObj->Action.ComDir = COMD_Stop;
307 Finish(fSuccess: true); return;
308 }
309
310 // Idles can't move to
311 if (cObj->Action.Act <= ActIdle)
312 {
313 Finish(); return;
314 }
315
316 // Action
317 switch (cObj->GetProcedure())
318 {
319 case DFA_WALK:
320 // Head to target
321 if (cx < Tx._getInt() - iTargetRange) cObj->Action.ComDir = COMD_Right;
322 if (cx > Tx._getInt() + iTargetRange) cObj->Action.ComDir = COMD_Left;
323 // Flight control
324 if (FlightControl()) return;
325 // Jump control
326 if (JumpControl()) return;
327 break;
328
329 case DFA_PUSH: case DFA_PULL:
330 // Head to target
331 if (cx < Tx._getInt() - iTargetRange) cObj->Action.ComDir = COMD_Right;
332 if (cx > Tx._getInt() + iTargetRange) cObj->Action.ComDir = COMD_Left;
333 break;
334
335 case DFA_SCALE:
336 // Head to target
337 if (cy > Ty + iTargetRange) cObj->Action.ComDir = COMD_Up;
338 if (cy < Ty - iTargetRange) cObj->Action.ComDir = COMD_Down;
339 // Let-Go Control
340 if (cObj->Action.Dir == DIR_Left)
341 {
342 // Target direction
343 if ((Tx._getInt() > cx + LetGoRange1) && (Inside(ival: cy - Ty, lbound: -LetGoRange2, rbound: +LetGoRange2)))
344 {
345 ObjectComLetGo(cObj, xdirf: +1); return;
346 }
347 // Contact (not if just started)
348 if (cObj->Action.Time > 2)
349 if (cObj->t_contact)
350 {
351 ObjectComLetGo(cObj, xdirf: +1); return;
352 }
353 }
354 if (cObj->Action.Dir == DIR_Right)
355 {
356 // Target direction
357 if ((Tx._getInt() < cx - LetGoRange1) && (Inside(ival: cy - Ty, lbound: -LetGoRange2, rbound: +LetGoRange2)))
358 {
359 ObjectComLetGo(cObj, xdirf: -1); return;
360 }
361 // Contact (not if just started)
362 if (cObj->Action.Time > 2)
363 if (cObj->t_contact)
364 {
365 ObjectComLetGo(cObj, xdirf: -1); return;
366 }
367 }
368 break;
369
370 case DFA_SWIM:
371 // Head to target
372 if (Tick2)
373 {
374 if (cx < Tx._getInt() - iTargetRange) cObj->Action.ComDir = COMD_Right;
375 if (cx > Tx._getInt() + iTargetRange) cObj->Action.ComDir = COMD_Left;
376 }
377 else
378 {
379 if (cy < Ty) cObj->Action.ComDir = COMD_Down;
380 if (cy > Ty) cObj->Action.ComDir = COMD_Up;
381 }
382 break;
383
384 case DFA_HANGLE:
385 // Head to target
386 if (cx < Tx._getInt() - iTargetRange) cObj->Action.ComDir = COMD_Right;
387 if (cx > Tx._getInt() + iTargetRange) cObj->Action.ComDir = COMD_Left;
388 // Let-Go Control
389 if (Abs(val: Angle(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty)) > LetGoHangleAngle)
390 ObjectComLetGo(cObj, xdirf: 0);
391 break;
392
393 case DFA_FLOAT:
394 {
395 C4Fixed dx = itofix(x: Tx._getInt()) - cObj->fix_x, dy = itofix(x: Ty) - cObj->fix_y;
396 // normalize
397 C4Fixed dScale = FIXED100(x: cObj->GetPhysical()->Float) / (std::max)(a: Abs(val: dx), b: Abs(val: dy));
398 dx *= dScale; dy *= dScale;
399 // difference to momentum
400 dx -= cObj->xdir; dy -= cObj->ydir;
401 // steer
402 if (Abs(val: dx) + Abs(val: dy) < FIXED100(x: 20)) cObj->Action.ComDir = COMD_None;
403 else if (Abs(val: dy) * 3 < dx) cObj->Action.ComDir = COMD_Right;
404 else if (Abs(val: dy) * 3 < -dx) cObj->Action.ComDir = COMD_Left;
405 else if (Abs(val: dx) * 3 < dy) cObj->Action.ComDir = COMD_Down;
406 else if (Abs(val: dx) * 3 < -dy) cObj->Action.ComDir = COMD_Up;
407 else if (dx > 0 && dy > 0) cObj->Action.ComDir = COMD_DownRight;
408 else if (dx < 0 && dy > 0) cObj->Action.ComDir = COMD_DownLeft;
409 else if (dx > 0 && dy < 0) cObj->Action.ComDir = COMD_UpRight;
410 else cObj->Action.ComDir = COMD_UpLeft;
411 }
412 break;
413
414 case DFA_FLIGHT:
415 // Flight control
416 if (FlightControl()) return;
417 break;
418 }
419}
420
421void C4Command::Dig()
422{
423 // Current object and target coordinates
424 int32_t cx, cy, tx, ty;
425 cx = cObj->x; cy = cObj->y;
426 tx = Tx._getInt(); ty = Ty + cObj->Shape.y + 3; // Target coordinates are bottom center
427 bool fDigOutMaterial = Data;
428
429 // Grabbing: let go
430 if (cObj->GetProcedure() == DFA_PUSH)
431 {
432 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
433 }
434
435 // If contained: exit
436 if (cObj->Contained)
437 {
438 cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
439 }
440
441 // Building or chopping: stop
442 if ((cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD))
443 ObjectComStop(cObj);
444
445 // Scaling or hangling: let go
446 if ((cObj->GetProcedure() == DFA_SCALE) || (cObj->GetProcedure() == DFA_HANGLE))
447 ObjectComLetGo(cObj, xdirf: (cObj->Action.Dir == DIR_Left) ? +1 : -1);
448
449 // Determine move-to range
450 int32_t iMoveToRange = MoveToRange;
451 if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
452
453 // Target reached: success
454 if (Inside(ival: cx - tx, lbound: -iMoveToRange, rbound: +iMoveToRange)
455 && Inside(ival: cy - ty, lbound: -iMoveToRange, rbound: +iMoveToRange))
456 {
457 ObjectComStop(cObj);
458 Finish(fSuccess: true); return;
459 }
460
461 // Can start digging only from walk
462 if (cObj->GetProcedure() != DFA_DIG)
463 if (cObj->GetProcedure() != DFA_WALK)
464 // Continue trying (assume currently in flight)
465 return;
466
467 // Start digging
468 if (cObj->GetProcedure() != DFA_DIG)
469 if (!ObjectComDig(cObj))
470 {
471 Finish(); return;
472 }
473
474 // Dig2Object
475 if (fDigOutMaterial) cObj->Action.Data = 1;
476
477 // Head to target
478 if (cx < tx - DigRange) cObj->Action.ComDir = COMD_Right;
479 if (cx > tx + DigRange) cObj->Action.ComDir = COMD_Left;
480 if (cy < ty - DigRange) cObj->Action.ComDir = COMD_Down;
481 if (cx < tx - DigRange) if (cy < ty - DigRange) cObj->Action.ComDir = COMD_DownRight;
482 if (cx > tx - DigRange) if (cy < ty - DigRange) cObj->Action.ComDir = COMD_DownLeft;
483 if (cx < tx - DigRange) if (cy > ty + DigRange) cObj->Action.ComDir = COMD_UpRight;
484 if (cx > tx + DigRange) if (cy > ty + DigRange) cObj->Action.ComDir = COMD_UpLeft;
485}
486
487void C4Command::Follow()
488{
489 // If crew member, only selected objects can follow
490 if (cObj->Def->CrewMember)
491 // Finish successfully to avoid fail message
492 if (!cObj->Select && cObj->Owner != NO_OWNER) { Finish(fSuccess: true); return; }
493
494 // No-one to follow
495 if (!Target) { Finish(); return; }
496
497 // Follow containment
498 if (cObj->Contained != Target->Contained)
499 {
500 // Only crew members can follow containment
501 if (!cObj->Def->CrewMember)
502 {
503 Finish(); return;
504 }
505 // Exit/enter
506 if (cObj->Contained) cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50);
507 else cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target->Contained, iTx: 0, iTy: 0, iUpdateInterval: 50);
508 return;
509 }
510
511 // Follow target grabbing
512 if (Target->GetProcedure() == DFA_PUSH)
513 {
514 // Grabbing same: copy ComDir
515 if (cObj->GetProcedure() == DFA_PUSH)
516 if (cObj->Action.Target == Target->Action.Target)
517 {
518 cObj->Action.ComDir = Target->Action.ComDir;
519 return;
520 }
521 // Else, grab target's grab
522 cObj->AddCommand(iCommand: C4CMD_Grab, pTarget: Target->Action.Target);
523 return;
524 }
525 else if (cObj->GetProcedure() == DFA_PUSH)
526 {
527 // Follow ungrabbing
528 cObj->AddCommand(iCommand: C4CMD_UnGrab);
529 return;
530 }
531
532 // If in following range
533 if (Inside<int32_t>(ival: cObj->x - Target->x, lbound: -FollowRange, rbound: +FollowRange)
534 && Inside<int32_t>(ival: cObj->y - Target->y, lbound: -FollowRange, rbound: +FollowRange))
535 {
536 // Copy target's Action.ComDir
537 cObj->Action.ComDir = Target->Action.ComDir;
538 }
539 else // Else, move to target
540 {
541 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Target->x, iTy: Target->y, iUpdateInterval: 10);
542 }
543}
544
545void C4Command::Enter()
546{
547 uint32_t ocf;
548
549 // No object to enter or can't enter by def
550 if (!Target || cObj->Def->NoPushEnter) { Finish(); return; }
551
552 // Already in target object
553 if (cObj->Contained == Target) { Finish(fSuccess: true); return; }
554
555 // Building or chopping: stop
556 if ((cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD))
557 ObjectComStop(cObj);
558
559 // Digging: stop
560 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
561
562 // Pushing grab only or pushing not desired: let go
563 if (cObj->GetProcedure() == DFA_PUSH)
564 if (cObj->Action.Target)
565 if (cObj->Action.Target->Def->Grab == 2 || !(Data & C4CMD_Enter_PushTarget))
566 {
567 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
568 }
569
570 // Pushing target: let go
571 if (cObj->GetProcedure() == DFA_PUSH)
572 if (cObj->Action.Target == Target)
573 {
574 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
575 }
576
577 // Grabbing overrides position for target
578 int32_t cx, cy;
579 cx = cObj->x; cy = cObj->y;
580 if (cObj->GetProcedure() == DFA_PUSH)
581 if (cObj->Action.Target)
582 {
583 cx = cObj->Action.Target->x; cy = cObj->Action.Target->y;
584 }
585
586 // If in entrance range
587 ocf = OCF_Entrance;
588 if (!cObj->Contained && Target->At(ctx: cx, cty: cy, ocf) && (ocf & OCF_Entrance))
589 {
590 // Stop
591 cObj->Action.ComDir = COMD_Stop;
592 // Pushing: push target into entrance, then stop
593 if ((cObj->GetProcedure() == DFA_PUSH) && cObj->Action.Target)
594 {
595 cObj->Action.Target->SetCommand(iCommand: C4CMD_Enter, pTarget: Target);
596 Finish(fSuccess: true); return;
597 }
598 // Else, enter self
599 else
600 {
601 // If entrance open, enter object
602 if (Target->EntranceStatus != 0)
603 {
604 cObj->Enter(pTarget: Target);
605 Finish(fSuccess: true); return;
606 }
607 else // Else, activate entrance
608 Target->ActivateEntrance(by_plr: cObj->Controller, by_obj: cObj);
609 }
610 }
611 else // Else, move to object's entrance
612 {
613 int32_t ex, ey, ewdt, ehgt;
614 if (Target->GetEntranceArea(aX&: ex, aY&: ey, aWdt&: ewdt, aHgt&: ehgt))
615 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: ex + ewdt / 2, iTy: ey + ehgt / 2, iUpdateInterval: 50, pTarget2: nullptr, fInitEvaluation: true, iData: (Data & C4CMD_Enter_PushTarget) ? C4CMD_MoveTo_PushTarget : 0);
616 }
617}
618
619void C4Command::Exit()
620{
621 // Outside: done
622 if (!cObj->Contained) { Finish(fSuccess: true); return; }
623
624 // Building: stop
625 if (cObj->GetProcedure() == DFA_BUILD)
626 ObjectComStop(cObj);
627
628 // Entrance open, leave object
629 if (cObj->Contained->EntranceStatus)
630 {
631 // Exit to container's container
632 if (cObj->Contained->Contained)
633 {
634 cObj->Enter(pTarget: cObj->Contained->Contained); Finish(fSuccess: true); return;
635 }
636 // Exit to entrance area
637 int32_t ex, ey, ewdt, ehgt;
638 if (cObj->Contained->OCF & OCF_Entrance)
639 if (cObj->Contained->GetEntranceArea(aX&: ex, aY&: ey, aWdt&: ewdt, aHgt&: ehgt))
640 {
641 cObj->Exit(iX: ex + ewdt / 2, iY: ey + ehgt + cObj->Shape.y - 1); Finish(fSuccess: true); return;
642 }
643 // Exit jump out of collection area
644 if (cObj->Def->Carryable)
645 if (cObj->Contained->Def->Collection.Wdt)
646 {
647 cObj->Exit(iX: cObj->Contained->x, iY: cObj->Contained->y + cObj->Contained->Def->Collection.y - 1);
648 ObjectComJump(cObj);
649 Finish(fSuccess: true); return;
650 }
651 // Plain exit
652 cObj->Exit(iX: cObj->x, iY: cObj->y);
653 Finish(fSuccess: true); return;
654 }
655
656 // Entrance closed, activate entrance
657 else
658 {
659 if (!cObj->Contained->ActivateEntrance(by_plr: cObj->Controller, by_obj: cObj))
660 // Entrance activation failed: fail
661 {
662 Finish(); return;
663 }
664 }
665}
666
667void C4Command::Grab()
668{
669 uint32_t ocf;
670 // Command fulfilled
671 if (cObj->GetProcedure() == DFA_PUSH)
672 if (cObj->Action.Target == Target)
673 {
674 Finish(fSuccess: true); return;
675 }
676 // Building or chopping: stop
677 if ((cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD))
678 ObjectComStop(cObj);
679 // Digging: stop
680 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
681 // Grabbing: let go
682 if (cObj->GetProcedure() == DFA_PUSH)
683 {
684 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
685 }
686 // No target
687 if (!Target) { Finish(); return; }
688 // At target object: grab
689 ocf = OCF_All;
690 if (!cObj->Contained && Target->At(ctx: cObj->x, cty: cObj->y, ocf))
691 {
692 // Scaling or hangling: let go
693 if ((cObj->GetProcedure() == DFA_SCALE) || (cObj->GetProcedure() == DFA_HANGLE))
694 ObjectComLetGo(cObj, xdirf: (cObj->Action.Dir == DIR_Left) ? +1 : -1);
695 if (!Target->Call(PSF_RejectGrabbed, pPars: {C4VObj(pObj: cObj)}).getBool())
696 {
697 // Grab
698 cObj->Action.ComDir = COMD_Stop;
699 ObjectComGrab(cObj, pTarget: Target);
700 }
701 else
702 {
703 // we don't want any failure message if this is a direct command
704 if (!GetBaseCommand())
705 {
706 BaseMode = C4CMD_Mode_SilentBase;
707 }
708 Finish(fSuccess: false);
709 }
710 }
711 // Else, move to object
712 else
713 {
714 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Target->x + Tx._getInt(), iTy: Target->y + Ty, iUpdateInterval: 50);
715 }
716}
717
718void C4Command::PushTo()
719{
720 // Target check
721 if (!Target) { Finish(); return; }
722
723 // Target is target self: fail
724 if (Target == Target2) { Finish(); return; }
725
726 // Command fulfilled
727 if (Target2)
728 {
729 // Object in correct target container: success
730 if (Target->Contained == Target2)
731 {
732 Finish(fSuccess: true); return;
733 }
734 }
735 else
736 {
737 // Object at target position: success
738 if (Inside<C4ValueInt>(ival: Target->x - Tx._getInt(), lbound: -PushToRange, rbound: +PushToRange))
739 if (Inside(ival: Target->y - Ty, lbound: -PushToRange, rbound: +PushToRange))
740 {
741 cObj->Action.ComDir = COMD_Stop;
742 cObj->AddCommand(iCommand: C4CMD_UnGrab);
743 cObj->AddCommand(iCommand: C4CMD_Wait, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 10);
744 Finish(fSuccess: true); return;
745 }
746 }
747
748 // Building or chopping: stop
749 if ((cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD))
750 ObjectComStop(cObj);
751
752 // Digging: stop
753 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
754
755 // Target contained: activate
756 if (Target->Contained)
757 {
758 cObj->AddCommand(iCommand: C4CMD_Activate, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 40); return;
759 }
760
761 // Grab target
762 if (!((cObj->GetProcedure() == DFA_PUSH) && (cObj->Action.Target == Target)))
763 {
764 cObj->AddCommand(iCommand: C4CMD_Grab, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 40); return;
765 }
766
767 // Move to target position / enter target object
768 if (Target2)
769 {
770 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target2, iTx: 0, iTy: 0, iUpdateInterval: 40, pTarget2: nullptr, fInitEvaluation: true, iData: C4CMD_Enter_PushTarget); return;
771 }
772 else
773 {
774 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Tx, iTy: Ty, iUpdateInterval: 40, pTarget2: nullptr, fInitEvaluation: true, iData: C4CMD_MoveTo_PushTarget); return;
775 }
776}
777
778void C4Command::Chop()
779{
780 uint32_t ocf;
781 // No target: fail
782 if (!Target) { Finish(); return; }
783 // Can not chop: fail
784 if (!cObj->GetPhysical()->CanChop)
785 {
786 Finish(); return;
787 }
788 // Target not chopable: done (assume was successfully chopped)
789 if (!(Target->OCF & OCF_Chop))
790 {
791 Finish(fSuccess: true); return;
792 }
793 // Chopping target: wait
794 if ((cObj->GetProcedure() == DFA_CHOP) && (cObj->Action.Target == Target))
795 return;
796 // Grabbing: let go
797 if (cObj->GetProcedure() == DFA_PUSH)
798 {
799 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
800 }
801 // Building, digging or chopping other: stop
802 if ((cObj->GetProcedure() == DFA_DIG) || (cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD))
803 {
804 ObjectComStop(cObj); return;
805 }
806 // At target object, in correct shopping position
807 ocf = OCF_All;
808 if (!cObj->Contained && Target->At(ctx: cObj->x, cty: cObj->y, ocf) && Inside<int32_t>(ival: Abs(val: cObj->x - Target->x), lbound: 4, rbound: 9))
809 {
810 cObj->Action.ComDir = COMD_Stop;
811 ObjectComChop(cObj, pTarget: Target);
812 }
813 // Else, move to object
814 else
815 {
816 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: (cObj->x > Target->x) ? Target->x + 6 : Target->x - 6, iTy: Target->y, iUpdateInterval: 50);
817 // Too close? Move away first.
818 if (Abs(val: cObj->x - Target->x) < 5)
819 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: (cObj->x > Target->x) ? Target->x + 15 : Target->x - 15, iTy: Target->y, iUpdateInterval: 50);
820 }
821}
822
823void C4Command::Build()
824{
825 uint32_t ocf;
826 // No target: cancel
827 if (!Target)
828 {
829 Finish(); return;
830 }
831 // Lost the ability to build? Fail.
832 if (cObj->GetPhysical() && !cObj->GetPhysical()->CanConstruct)
833 {
834 Finish(fSuccess: false, szFailMessage: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_CANTBUILD, args: cObj->GetName()).c_str());
835 return;
836 }
837 // Target complete: Command fulfilled
838 if (Target->GetCon() >= FullCon)
839 {
840 // Activate internal vehicles
841 if (Target->Contained && (Target->Category & C4D_Vehicle))
842 cObj->AddCommand(iCommand: C4CMD_Activate, pTarget: Target);
843 // Energy supply (if necessary and nobody else is doing so already)
844 if (Game.Rules & C4RULE_StructuresNeedEnergy)
845 if (Target->Def->LineConnect & C4D_Power_Input)
846 if (!Game.FindObjectByCommand(iCommand: C4CMD_Energy, pTarget: Target))
847 {
848 // if another Clonk is also building this structure and carries a linekit already, that Clonk should rather perform the energy command
849 C4Object *pOtherBuilder = nullptr;
850 if (!cObj->Contents.Find(id: C4ID_Linekit))
851 {
852 while (pOtherBuilder = Game.FindObjectByCommand(iCommand: C4CMD_Build, pTarget: Target, iTx: C4VNull, iTy: 0, pTarget2: nullptr, pFindNext: pOtherBuilder))
853 if (pOtherBuilder->Contents.Find(id: C4ID_Linekit))
854 break;
855 }
856 if (!pOtherBuilder)
857 cObj->AddCommand(iCommand: C4CMD_Energy, pTarget: Target);
858 }
859 // Done
860 cObj->Action.ComDir = COMD_Stop;
861 Finish(fSuccess: true); return;
862 }
863 // Currently working on target: continue
864 if (cObj->GetProcedure() == DFA_BUILD)
865 if (cObj->Action.Target == Target)
866 return;
867 // Grabbing: let go
868 if (cObj->GetProcedure() == DFA_PUSH)
869 {
870 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
871 }
872 // Digging: stop
873 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
874 // Worker ist structure or static back: internal target build only (old stuff)
875 if ((cObj->Category & C4D_Structure) || (cObj->Category & C4D_StaticBack))
876 {
877 // Target is internal
878 if (Target->Contained == cObj)
879 {
880 ObjectComBuild(cObj, pTarget: Target); return;
881 }
882 // Target is not internal: cancel
883 Finish(); return;
884 }
885 // At target check
886 ocf = OCF_All;
887 if ((Target->Contained && (cObj->Contained == Target->Contained))
888 || (Target->At(ctx: cObj->x, cty: cObj->y, ocf) && (cObj->GetProcedure() == DFA_WALK)))
889 {
890 ObjectComStop(cObj);
891 ObjectComBuild(cObj, pTarget: Target);
892 return;
893 }
894 // Else, move to object
895 else
896 {
897 if (Target->Contained) cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target->Contained, iTx: 0, iTy: 0, iUpdateInterval: 50);
898 else cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Target->x, iTy: Target->y, iUpdateInterval: 50);
899 return;
900 }
901}
902
903void C4Command::UnGrab()
904{
905 ObjectComUnGrab(cObj);
906 cObj->Action.ComDir = COMD_Stop;
907 Finish(fSuccess: true);
908}
909
910void C4Command::Throw()
911{
912 // Digging: stop
913 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
914
915 // Throw specific object not in contents: get object
916 if (Target)
917 if (!cObj->Contents.GetLink(pObj: Target))
918 {
919 cObj->AddCommand(iCommand: C4CMD_Get, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 40);
920 return;
921 }
922
923 // Determine move-to range
924 int32_t iMoveToRange = MoveToRange;
925 if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
926
927 // Target coordinates are not default 0/0: targeted throw
928 if ((Tx._getInt() != 0) || (Ty != 0))
929 {
930 // Grabbing: let go
931 if (cObj->GetProcedure() == DFA_PUSH)
932 {
933 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
934 }
935
936 // Preferred throwing direction
937 int32_t iDir = +1; if (cObj->x > Tx._getInt()) iDir = -1;
938
939 // Find throwing position
940 int32_t iTx, iTy;
941 C4Fixed pthrow = ValByPhysical(iPercent: 400, iPhysical: cObj->GetPhysical()->Throw);
942 int32_t iHeight = cObj->Shape.Hgt;
943 if (!FindThrowingPosition(iTx: Tx._getInt(), iTy: Ty, fXDir: pthrow * iDir, fYDir: -pthrow, iHeight, rX&: iTx, rY&: iTy))
944 if (!FindThrowingPosition(iTx: Tx._getInt(), iTy: Ty, fXDir: pthrow * iDir * -1, fYDir: -pthrow, iHeight, rX&: iTx, rY&: iTy))
945 // No throwing position: fail
946 {
947 Finish(); return;
948 }
949
950 // At throwing position: face target and throw
951 if (Inside<int32_t>(ival: cObj->x - iTx, lbound: -iMoveToRange, rbound: +iMoveToRange) && Inside<int32_t>(ival: cObj->y - iTy, lbound: -15, rbound: +15))
952 {
953 if (cObj->x < Tx._getInt()) cObj->SetDir(DIR_Right); else cObj->SetDir(DIR_Left);
954 cObj->Action.ComDir = COMD_Stop;
955 if (ObjectComThrow(cObj, pThing: Target))
956 Finish(fSuccess: true); // Throw successfull: done, else continue
957 return;
958 }
959
960 // Move to target position
961 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx, iTy, iUpdateInterval: 20);
962
963 return;
964 }
965
966 // Contained: put or take
967 if (cObj->Contained)
968 {
969 ObjectComPutTake(cObj, pTarget: cObj->Contained, pThing: Target);
970 Finish(fSuccess: true); return;
971 }
972
973 // Pushing: put or take
974 if (cObj->GetProcedure() == DFA_PUSH)
975 {
976 if (cObj->Action.Target)
977 ObjectComPutTake(cObj, pTarget: cObj->Action.Target, pThing: Target);
978 Finish(fSuccess: true); return;
979 }
980
981 // Outside: Throw
982 ObjectComThrow(cObj, pThing: Target);
983 Finish(fSuccess: true);
984}
985
986void C4Command::Take()
987{
988 ObjectComTake(cObj);
989 Finish(fSuccess: true);
990}
991
992void C4Command::Take2()
993{
994 ObjectComTake2(cObj);
995 Finish(fSuccess: true);
996}
997
998void C4Command::Drop()
999{
1000 // Digging: stop
1001 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
1002
1003 // Drop specific object not in contents: get object
1004 if (Target)
1005 if (!cObj->Contents.GetLink(pObj: Target))
1006 {
1007 cObj->AddCommand(iCommand: C4CMD_Get, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 40);
1008 return;
1009 }
1010
1011 // Determine move-to range
1012 int32_t iMoveToRange = MoveToRange;
1013 if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
1014
1015 // Target coordinates are not default 0/0: targeted drop
1016 if ((Tx._getInt() != 0) || (Ty != 0))
1017 {
1018 // Grabbing: let go
1019 if (cObj->GetProcedure() == DFA_PUSH)
1020 {
1021 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1022 }
1023 // At target position: drop
1024 if (Inside<int32_t>(ival: cObj->x - Tx._getInt(), lbound: -iMoveToRange, rbound: +iMoveToRange) && Inside<int32_t>(ival: cObj->y - Ty, lbound: -15, rbound: +15))
1025 {
1026 cObj->Action.ComDir = COMD_Stop;
1027 ObjectComDrop(cObj, pThing: Target);
1028 Finish(fSuccess: true);
1029 return;
1030 }
1031 // Move to target position
1032 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Tx._getInt(), iTy: Ty, iUpdateInterval: 20);
1033 return;
1034 }
1035
1036 // Contained: put
1037 if (cObj->Contained)
1038 {
1039 ObjectComPutTake(cObj, pTarget: cObj->Contained, pThing: Target);
1040 Finish(fSuccess: true); return;
1041 }
1042
1043 // Pushing: put
1044 if (cObj->GetProcedure() == DFA_PUSH)
1045 {
1046 if (cObj->Action.Target)
1047 ObjectComPutTake(cObj, pTarget: cObj->Action.Target, pThing: Target);
1048 Finish(fSuccess: true); return;
1049 }
1050
1051 // Outside: Drop
1052 ObjectComDrop(cObj, pThing: Target);
1053 Finish(fSuccess: true);
1054}
1055
1056void C4Command::Jump()
1057{
1058 // Tx not default 0: adjust jump direction
1059 if (Tx._getInt())
1060 {
1061 if (Tx._getInt() < cObj->x) cObj->SetDir(DIR_Left);
1062 if (Tx._getInt() > cObj->x) cObj->SetDir(DIR_Right);
1063 }
1064 // Jump
1065 ObjectComJump(cObj);
1066 // Done
1067 Finish(fSuccess: true);
1068}
1069
1070void C4Command::Wait()
1071{
1072 // Digging: stop
1073 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
1074}
1075
1076void C4Command::Context()
1077{
1078 // Not context object specified (in Target2): fail
1079 if (!Target2) { Finish(); return; }
1080 // Open context menu for target
1081 cObj->ActivateMenu(iMenu: C4MN_Context, iMenuSelect: 0, iMenuData: 0, iMenuPosition: 0, pTarget: Target2);
1082 if (Tx._getInt() != 0 && Ty != 0)
1083 if (cObj->Menu)
1084 {
1085 cObj->Menu->SetAlignment(C4MN_Align_Free);
1086 cObj->Menu->SetLocation(iX: Tx._getInt(), iY: Ty);
1087 }
1088 // Done
1089 Finish(fSuccess: true);
1090}
1091
1092bool C4Command::GetTryEnter()
1093{
1094 // No minimum con knowledge vehicles/items: fail
1095 if (Target->Contained && CheckMinimumCon(pObj: Target)) { /* fail??! */ return false; }
1096 // Target contained and container has RejectContents: fail
1097 if (Target->Contained && Target->Contained->Call(PSF_RejectContents)) { Finish(); return false; }
1098 // Collection limit: drop other object
1099 // return after drop, so multiple objects may be dropped
1100 if (cObj->Def->CollectionLimit && (cObj->Contents.ObjectCount() >= cObj->Def->CollectionLimit))
1101 {
1102 if (!cObj->PutAwayUnusedObject(pToMakeRoomForObject: Target)) { Finish(); return false; }
1103 return false;
1104 }
1105 bool fWasContained = !!Target->Contained;
1106 // Grab target object
1107 bool fRejectCollect = false;
1108 bool fSuccess = !!Target->Enter(pTarget: cObj, fCalls: true, fCopyMotion: true, pfRejectCollect: &fRejectCollect);
1109 // target is void?
1110 // well...most likely the new container has done something with it
1111 // so count it as success
1112 if (!Target) { Finish(fSuccess: true); return true; }
1113 // collection rejected by target: make room for more contents
1114 if (fRejectCollect)
1115 {
1116 if (cObj->PutAwayUnusedObject(pToMakeRoomForObject: Target)) return false;
1117 // Can't get due to RejectCollect: fail
1118 Finish();
1119 return false;
1120 }
1121 // if not successfully entered for any other reason, fail
1122 if (!fSuccess) { Finish(); return false; }
1123 // get-callback for getting out of containers
1124 if (fWasContained) cObj->Call(PSF_Get, pPars: {C4VObj(pObj: Target)});
1125 // entered
1126 return true;
1127}
1128
1129void C4Command::Get()
1130{
1131 // Data set and target specified: open get menu & done (old style)
1132 if (((Data == 1) || (Data == 2)) && Target)
1133 {
1134 cObj->ActivateMenu(iMenu: (Data == 1) ? C4MN_Get : C4MN_Contents, iMenuSelect: 0, iMenuData: 0, iMenuPosition: 0, pTarget: Target);
1135 Finish(fSuccess: true); return;
1136 }
1137
1138 // Get target specified by container and type
1139 if (!Target && Target2 && Data)
1140 if (!(Target = Target2->Contents.Find(id: Data)))
1141 {
1142 Finish(); return;
1143 }
1144
1145 // No target: failure
1146 if (!Target) { Finish(); return; }
1147
1148 // Target not carryable: failure
1149 if (!(Target->OCF & OCF_Carryable))
1150 {
1151 Finish(); return;
1152 }
1153
1154 // Target collected
1155 if (Target->Contained == cObj)
1156 // Get-count specified: decrease count and continue with next object
1157 if (Tx._getInt() > 1)
1158 {
1159 Target = nullptr; Tx--; return;
1160 }
1161 // We're done
1162 else
1163 {
1164 cObj->Action.ComDir = COMD_Stop; Finish(fSuccess: true); return;
1165 }
1166
1167 // Grabbing other than target container: let go
1168 if (cObj->GetProcedure() == DFA_PUSH)
1169 if (cObj->Action.Target != Target->Contained)
1170 {
1171 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1172 }
1173
1174 // Target in solid: dig out
1175 if (!Target->Contained && (Target->OCF & OCF_InSolid))
1176 {
1177 // Check for closest free position
1178 int32_t iX = Target->x, iY = Target->y;
1179 // Find all-closest dig-out position
1180 if (!FindClosestFree(rX&: iX, rY&: iY, iAngle1: -120, iAngle2: +120, iExcludeAngle1: -1, iExcludeAngle2: -1))
1181 // None found
1182 {
1183 Finish(); return;
1184 }
1185 // Check good-angle left/right dig-out position
1186 int32_t iX2 = Target->x, iY2 = Target->y;
1187 if (FindClosestFree(rX&: iX2, rY&: iY2, iAngle1: -140, iAngle2: +140, iExcludeAngle1: -40, iExcludeAngle2: +40))
1188 // Use good-angle position if it's not way worse
1189 if (Distance(iX1: Target->x, iY1: Target->y, iX2, iY2) < 10 * Distance(iX1: Target->x, iY1: Target->y, iX2: iX, iY2: iY))
1190 {
1191 iX = iX2; iY = iY2;
1192 }
1193 // Move to closest free position (if not in dig-direct range)
1194 if (!Inside(ival: cObj->x - iX, lbound: -DigOutPositionRange, rbound: +DigOutPositionRange)
1195 || !Inside(ival: cObj->y - iY, lbound: -DigOutPositionRange, rbound: +DigOutPositionRange))
1196 {
1197 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: iX, iTy: iY, iUpdateInterval: 50); return;
1198 }
1199 // DigTo
1200 cObj->AddCommand(iCommand: C4CMD_Dig, pTarget: nullptr, iTx: Target->x, iTy: Target->y + 4, iUpdateInterval: 50); return;
1201 }
1202
1203 // Digging: stop
1204 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
1205
1206 // Target contained
1207 if (Target->Contained)
1208 {
1209 // target can't be taken out of containers?
1210 if (Target->Def->NoGet) return;
1211 // In same container: grab target
1212 if (cObj->Contained == Target->Contained)
1213 {
1214 GetTryEnter();
1215 // Done
1216 return;
1217 }
1218
1219 // Leave own container
1220 if (cObj->Contained)
1221 {
1222 cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1223 }
1224
1225 // Target container has grab get: grab target container
1226 if (Target->Contained->Def->GrabPutGet & C4D_Grab_Get)
1227 {
1228 // Grabbing target container
1229 if ((cObj->GetProcedure() == DFA_PUSH) && (cObj->Action.Target == Target->Contained))
1230 {
1231 GetTryEnter();
1232 // Done
1233 return;
1234 }
1235 // Grab target container
1236 cObj->AddCommand(iCommand: C4CMD_Grab, pTarget: Target->Contained, iTx: 0, iTy: 0, iUpdateInterval: 50);
1237 return;
1238 }
1239
1240 // Target container has entrance: enter target container
1241 if (Target->Contained->OCF & OCF_Entrance)
1242 {
1243 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target->Contained, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1244 }
1245
1246 // Can't get to target due to target container: fail
1247 Finish();
1248 return;
1249 }
1250
1251 // Target outside
1252
1253 // Leave own container
1254 if (cObj->Contained) { cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return; }
1255
1256 // Outside
1257 if (!cObj->Contained)
1258 {
1259 // Target in collection range
1260 uint32_t ocf = OCF_Normal | OCF_Collection;
1261 if (cObj->At(ctx: Target->x, cty: Target->y, ocf))
1262 {
1263 // stop here
1264 cObj->Action.ComDir = COMD_Stop;
1265 // try getting the object
1266 if (GetTryEnter()) return;
1267 }
1268
1269 // Target not in range
1270 else
1271 {
1272 // Target in jumping range above clonk: try side-move jump
1273 if (Inside<int32_t>(ival: cObj->x - Target->x, lbound: -10, rbound: +10))
1274 if (Inside<int32_t>(ival: cObj->y - Target->y, lbound: 30, rbound: 50))
1275 {
1276 int32_t iSideX = 1; if (Random(iRange: 2)) iSideX = -1;
1277 iSideX = cObj->x + iSideX * (cObj->y - Target->y);
1278 if (PathFree(x1: iSideX, y1: cObj->y, x2: Target->x, y2: Target->y))
1279 {
1280 // Side-move jump
1281 cObj->AddCommand(iCommand: C4CMD_Jump, pTarget: nullptr, iTx: Tx._getInt(), iTy: Ty);
1282 if (cObj->Def->CollectionLimit && (cObj->Contents.ObjectCount() >= cObj->Def->CollectionLimit))
1283 cObj->AddCommand(iCommand: C4CMD_Drop); // Drop object if necessary due to collection limit
1284 // Need to kill NoCollectDelay after drop...!
1285 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: iSideX, iTy: cObj->y, iUpdateInterval: 50);
1286 }
1287 }
1288 // Move to target (random offset for difficult pickups)
1289 // ...avoid offsets into solid which would lead to high above surface locations!
1290 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Target->x + Random(iRange: 15) - 7, iTy: Target->y, iUpdateInterval: 25, pTarget2: nullptr);
1291 }
1292 }
1293}
1294
1295bool C4Command::CheckMinimumCon(C4Object *pObj)
1296{
1297 if ((pObj->Category & C4D_Vehicle) || (pObj->Category & C4D_Object))
1298 if (pObj->Category & C4D_SelectKnowledge)
1299 if (pObj->GetCon() < FullCon)
1300 {
1301 Finish(fSuccess: false, szFailMessage: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_NOCONACTIV, args: pObj->GetName()).c_str());
1302 return true;
1303 }
1304 return false;
1305}
1306
1307void C4Command::Activate()
1308{
1309 // Container specified, but no Target & no type: open activate menu for container
1310 if (Target2 && !Target && !Data)
1311 {
1312 cObj->ActivateMenu(iMenu: C4MN_Activate, iMenuSelect: 0, iMenuData: 0, iMenuPosition: 0, pTarget: Target2);
1313 Finish(fSuccess: true);
1314 return;
1315 }
1316
1317 // Target object specified & outside: success
1318 if (Target)
1319 if (!Target->Contained)
1320 {
1321 Finish(fSuccess: true); return;
1322 }
1323
1324 // No container specified: determine container by target object
1325 if (!Target2)
1326 if (Target)
1327 Target2 = Target->Contained;
1328
1329 // No container specified: fail
1330 if (!Target2) { Finish(); return; }
1331
1332 // Digging: stop
1333 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
1334
1335 // In container
1336 if (cObj->Contained == Target2)
1337 {
1338 for (Tx.SetInt(Data ? std::max<int32_t>(a: Tx._getInt(), b: 1) : 1); Tx._getInt(); --Tx)
1339 {
1340 // If not specified get object from target contents by type
1341 // Find first object requested id that has no command exit yet
1342 C4Object *pObj; C4ObjectLink *cLnk;
1343 if (!Target)
1344 for (cLnk = Target2->Contents.First; cLnk && (pObj = cLnk->Obj); cLnk = cLnk->Next)
1345 if (pObj->Status && (pObj->Def->id == static_cast<C4ID>(Data)))
1346 if (!pObj->Command || (pObj->Command->Command != C4CMD_Exit))
1347 {
1348 Target = pObj; break;
1349 }
1350 // No target
1351 if (!Target) { Finish(); return; }
1352
1353 // Thing in own container (target2)
1354 if (Target->Contained != Target2) { Finish(); return; }
1355
1356 // No minimum con knowledge vehicles/items
1357 if (CheckMinimumCon(pObj: Target)) return;
1358
1359 // Activate object to exit
1360 Target->Controller = cObj->Controller;
1361 Target->SetCommand(iCommand: C4CMD_Exit);
1362 Target = nullptr;
1363 }
1364
1365 Finish(fSuccess: true); return;
1366 }
1367
1368 // Leave own container
1369 if (cObj->Contained)
1370 {
1371 cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1372 }
1373
1374 // Target container has entrance: enter
1375 if (Target2->OCF & OCF_Entrance)
1376 {
1377 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target2, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1378 }
1379
1380 // Can't get to target due to target container: fail
1381 Finish();
1382}
1383
1384void C4Command::Put() // Notice: Put command is currently using Ty as an internal reminder flag for letting go after putting...
1385{
1386 // No target container: failure
1387 if (!Target) { Finish(); return; }
1388
1389 // Thing to put specified by type
1390 if (!Target2 && Data)
1391 if (!(Target2 = cObj->Contents.Find(id: Data)))
1392 {
1393 Finish(); return;
1394 }
1395
1396 // No thing to put specified
1397 if (!Target2)
1398 // Assume first contents object
1399 if (!(Target2 = cObj->Contents.GetObject()))
1400 // No contents object to put - most likely we did have a target but it was deleted,
1401 // e.g. by AutoSellContents in a base. New behaviour: if there is nothing to put, we
1402 // now consider the command succesfully completed.
1403 {
1404 Finish(fSuccess: true); return;
1405 }
1406
1407 // Thing is in target
1408 if (Target2->Contained == Target)
1409 // Put-count specified: decrease count and continue with next object
1410 if (Tx._getInt() > 1)
1411 {
1412 Target2 = nullptr; Tx--; return;
1413 }
1414 // We're done
1415 else
1416 {
1417 Finish(fSuccess: true); return;
1418 }
1419
1420 // Thing to put not in contents: get object
1421 if (!cObj->Contents.GetLink(pObj: Target2))
1422 {
1423 // Object is nearby and traveling: wait
1424 if (!Target2->Contained)
1425 if (Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target2->x, iY2: Target2->y) < 80)
1426 if (Target2->OCF & OCF_HitSpeed1)
1427 return;
1428 // Go get it
1429 cObj->AddCommand(iCommand: C4CMD_Get, pTarget: Target2, iTx: 0, iTy: 0, iUpdateInterval: 40); return;
1430 }
1431
1432 // Target is contained: can't do it
1433 if (Target->Contained)
1434 {
1435 Finish(); return;
1436 }
1437
1438 // Digging: stop
1439 if (cObj->GetProcedure() == DFA_DIG) ObjectComStop(cObj);
1440
1441 // Grabbing other than target: let go
1442 C4Object *pGrabbing = nullptr;
1443 if (cObj->GetProcedure() == DFA_PUSH)
1444 pGrabbing = cObj->Action.Target;
1445 if (pGrabbing && (pGrabbing != Target))
1446 {
1447 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1448 }
1449
1450 // Inside target container
1451 if (cObj->Contained == Target)
1452 {
1453 // Try to put
1454 if (!ObjectComPut(cObj, pTarget: Target, pThing: Target2))
1455 Finish(); // Putting failed
1456 return;
1457 }
1458
1459 // Leave own container
1460 if (cObj->Contained)
1461 {
1462 cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1463 }
1464
1465 // Target has collection: throw in if not fragile, not grabbing target and throwing position found
1466 if (Target->OCF & OCF_Collection)
1467 if (!Target2->Def->Fragile)
1468 if (pGrabbing != Target)
1469 {
1470 int32_t iTx = Target->x + Target->Def->Collection.x + Target->Def->Collection.Wdt / 2;
1471 int32_t iTy = Target->y + Target->Def->Collection.y + Target->Def->Collection.Hgt / 2;
1472 C4Fixed pthrow = ValByPhysical(iPercent: 400, iPhysical: cObj->GetPhysical()->Throw);
1473 int32_t iHeight = cObj->Shape.Hgt;
1474 int32_t iPosX, iPosY;
1475 int32_t iObjDist = Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target->x, iY2: Target->y);
1476 if ((FindThrowingPosition(iTx, iTy, fXDir: pthrow, fYDir: -pthrow, iHeight, rX&: iPosX, rY&: iPosY) && (Distance(iX1: iPosX, iY1: iPosY, iX2: cObj->x, iY2: cObj->y) < iObjDist))
1477 || (FindThrowingPosition(iTx, iTy, fXDir: pthrow * -1, fYDir: -pthrow, iHeight, rX&: iPosX, rY&: iPosY) && (Distance(iX1: iPosX, iY1: iPosY, iX2: cObj->x, iY2: cObj->y) < iObjDist)))
1478 {
1479 // Throw
1480 cObj->AddCommand(iCommand: C4CMD_Throw, pTarget: Target2, iTx, iTy, iUpdateInterval: 5);
1481 return;
1482 }
1483 }
1484
1485 // Target has C4D_Grab_Put: grab target and put
1486 if (Target->Def->GrabPutGet & C4D_Grab_Put)
1487 {
1488 // Grabbing target container
1489 if (pGrabbing == Target)
1490 {
1491 // Try to put
1492 if (!ObjectComPut(cObj, pTarget: Target, pThing: Target2))
1493 // Putting failed
1494 {
1495 Finish(); return;
1496 }
1497 // Let go (if we grabbed the target because of this command)
1498 if (Ty) cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0);
1499 return;
1500 }
1501 // Grab target and let go after putting
1502 cObj->AddCommand(iCommand: C4CMD_Grab, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 50);
1503 Ty = 1;
1504 return;
1505 }
1506
1507 // Target can be entered: enter target
1508 if (Target->OCF & OCF_Entrance)
1509 {
1510 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1511 }
1512}
1513
1514void C4Command::ClearPointers(C4Object *pObj)
1515{
1516 if (cObj == pObj) cObj = nullptr;
1517 if (Target == pObj) Target = nullptr;
1518 if (Target2 == pObj) Target2 = nullptr;
1519}
1520
1521void C4Command::Execute()
1522{
1523 // Finished?!
1524 if (Finished) return;
1525
1526 // Parent object safety
1527 if (!cObj) { Finish(); return; }
1528
1529 // Delegated command failed
1530 if (Failures)
1531 {
1532 // Retry
1533 if (Retries > 0)
1534 {
1535 Failures = 0; Retries--; cObj->AddCommand(iCommand: C4CMD_Retry, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 10); return;
1536 }
1537 // Too many failures
1538 else
1539 {
1540 Finish(); return;
1541 }
1542 }
1543
1544 // Command update
1545 if (UpdateInterval > 0)
1546 {
1547 UpdateInterval--;
1548 if (UpdateInterval == 0)
1549 {
1550 Finish(fSuccess: true); return;
1551 }
1552 }
1553
1554 // Initial command evaluation
1555 if (InitEvaluation()) return;
1556
1557 // from now on, we are executing this command... and nobody
1558 // should dare deleting us
1559 iExec = 1;
1560
1561 // Execute
1562 switch (Command)
1563 {
1564 case C4CMD_Follow: Follow(); break;
1565 case C4CMD_MoveTo: MoveTo(); break;
1566 case C4CMD_Enter: Enter(); break;
1567 case C4CMD_Exit: Exit(); break;
1568 case C4CMD_Grab: Grab(); break;
1569 case C4CMD_UnGrab: UnGrab(); break;
1570 case C4CMD_Throw: Throw(); break;
1571 case C4CMD_Chop: Chop(); break;
1572 case C4CMD_Build: Build(); break;
1573 case C4CMD_Jump: Jump(); break;
1574 case C4CMD_Wait: Wait(); break;
1575 case C4CMD_Get: Get(); break;
1576 case C4CMD_Put: Put(); break;
1577 case C4CMD_Drop: Drop(); break;
1578 case C4CMD_Dig: Dig(); break;
1579 case C4CMD_Activate: Activate(); break;
1580 case C4CMD_PushTo: PushTo(); break;
1581 case C4CMD_Construct: Construct(); break;
1582 case C4CMD_Transfer: Transfer(); break;
1583 case C4CMD_Attack: Attack(); break;
1584 case C4CMD_Context: Context(); break;
1585 case C4CMD_Buy: Buy(); break;
1586 case C4CMD_Sell: Sell(); break;
1587 case C4CMD_Acquire: Acquire(); break;
1588 case C4CMD_Energy: Energy(); break;
1589 case C4CMD_Retry: Retry(); break;
1590 case C4CMD_Home: Home(); break;
1591 case C4CMD_Call: Call(); break;
1592 case C4CMD_Take: Take(); break; // carlo
1593 case C4CMD_Take2: Take2(); break; // carlo
1594 default: Finish(); break;
1595 }
1596
1597 // Remember this command might have already been deleted through calls
1598 // made during execution. You must not do anything here...
1599
1600 // check: command must be deleted?
1601 if (iExec > 1)
1602 delete this;
1603 else
1604 iExec = 0;
1605}
1606
1607void C4Command::Finish(bool fSuccess, const char *szFailMessage)
1608{
1609 // Mark finished
1610 Finished = true;
1611 // Failed
1612 if (!fSuccess)
1613 Fail(szFailMessage);
1614 else
1615 {
1616 // successful commands might gain EXP
1617 int32_t iExpGain = GetExpGain();
1618 if (iExpGain && cObj) if (cObj->Info)
1619 for (int32_t i = iExpGain; i; --i)
1620 if (!(++cObj->Info->ControlCount % 5))
1621 cObj->DoExperience(change: 1);
1622 }
1623}
1624
1625bool C4Command::InitEvaluation()
1626{
1627 // Already evaluated
1628 if (Evaluated) return false;
1629 // Set evaluation flag
1630 Evaluated = true;
1631 // Evaluate
1632 switch (Command)
1633 {
1634 case C4CMD_MoveTo:
1635 {
1636 // Target coordinates by Target
1637 if (Target) { Tx += Target->x; Ty += Target->y; Target = nullptr; }
1638 // Adjust coordinates
1639 int32_t iTx = Tx._getInt();
1640 if (~Data & C4CMD_MoveTo_NoPosAdjust) AdjustMoveToTarget(rX&: iTx, rY&: Ty, fFreeMove: FreeMoveTo(cObj), iShapeHgt: cObj->Shape.Hgt);
1641 Tx.SetInt(iTx);
1642 return true;
1643 }
1644
1645 case C4CMD_PushTo:
1646 {
1647 // Adjust coordinates
1648 int32_t iTx = Tx._getInt();
1649 AdjustMoveToTarget(rX&: iTx, rY&: Ty, fFreeMove: FreeMoveTo(cObj), iShapeHgt: cObj->Shape.Hgt);
1650 Tx.SetInt(iTx);
1651 return true;
1652 }
1653
1654 case C4CMD_Exit:
1655 // Cancel attach
1656 ObjectComCancelAttach(cObj);
1657 return true;
1658
1659 case C4CMD_Wait:
1660 // Update interval by Data
1661 if (Data) UpdateInterval = Data;
1662 // Else update interval by Tx
1663 else if (Tx._getInt()) UpdateInterval = Tx._getInt();
1664 return true;
1665
1666 case C4CMD_Acquire:
1667 // update default search range
1668 if (!Tx._getInt()) Tx.SetInt(500);
1669 if (!Ty) Ty = 250;
1670 return true;
1671 }
1672 // Need not be evaluated
1673 return false;
1674}
1675
1676void C4Command::Clear()
1677{
1678 Command = C4CMD_None;
1679 cObj = nullptr;
1680 Evaluated = false;
1681 PathChecked = false;
1682 Tx = C4VNull;
1683 Ty = 0;
1684 Target = Target2 = nullptr;
1685 UpdateInterval = 0;
1686 Text.clear();
1687 BaseMode = C4CMD_Mode_SilentSub;
1688}
1689
1690void C4Command::Construct()
1691{
1692 // Only those who can
1693 if (cObj->GetPhysical() && !cObj->GetPhysical()->CanConstruct)
1694 {
1695 Finish(fSuccess: false, szFailMessage: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_CANTBUILD, args: cObj->GetName()).c_str());
1696 return;
1697 }
1698 // No target type to construct: open menu & done
1699 if (!Data)
1700 {
1701 cObj->ActivateMenu(iMenu: C4MN_Construction);
1702 Finish(fSuccess: true); return;
1703 }
1704
1705 // Determine move-to range
1706 int32_t iMoveToRange = MoveToRange;
1707 if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
1708
1709 // this is a secondary construct command (i.e., help with construction)?
1710 if (Target)
1711 {
1712 // check if target is building something
1713 C4Command *pBuildCmd = Target->FindCommand(iCommandType: C4CMD_Build);
1714 if (pBuildCmd)
1715 {
1716 // then help
1717 Finish(fSuccess: true);
1718 cObj->AddCommand(iCommand: C4CMD_Build, pTarget: pBuildCmd->Target);
1719 }
1720 // construct command still present? (might find another stacked command, which doesn't really matter for now...)
1721 if (!Target->FindCommand(iCommandType: C4CMD_Construct))
1722 // command aborted (or done?): failed to help; don't issue another construct command, because it is likely to fail anyway
1723 // (and maybe, it had been finished while this Clonk was still moving to the site)
1724 {
1725 Finish(fSuccess: false); return;
1726 }
1727 // site not yet placed: move to target, if necessary and known
1728 if (Tx._getInt() || Ty)
1729 if (!Inside<int32_t>(ival: cObj->x - Tx._getInt(), lbound: -iMoveToRange, rbound: +iMoveToRange) || !Inside<int32_t>(ival: cObj->y - Ty, lbound: -20, rbound: +20))
1730 {
1731 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Tx, iTy: Ty, iUpdateInterval: 50); return;
1732 }
1733 // at target construction site and site not yet placed: wait
1734 cObj->AddCommand(iCommand: C4CMD_Wait, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 10);
1735 return;
1736 }
1737
1738 // No valid target type: fail
1739 C4Def *pDef; if (!(pDef = C4Id2Def(id: Data))) { Finish(); return; }
1740
1741 // player has knowledge of this construction?
1742 C4Player *pPlayer = Game.Players.Get(iPlayer: cObj->Owner);
1743 if (pPlayer) if (!pPlayer->Knowledge.GetIDCount(id: Data, zeroDefVal: 1)) { Finish(); return; }
1744
1745 // Building, chopping, digging: stop
1746 if ((cObj->GetProcedure() == DFA_CHOP) || (cObj->GetProcedure() == DFA_BUILD) || (cObj->GetProcedure() == DFA_DIG))
1747 ObjectComStop(cObj);
1748
1749 // Pushing: let go
1750 if (cObj->GetProcedure() == DFA_PUSH)
1751 if (cObj->Action.Target)
1752 {
1753 cObj->AddCommand(iCommand: C4CMD_UnGrab, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
1754 }
1755
1756 // No construction site specified: find one
1757 if ((Tx._getInt() == 0) && (Ty == 0))
1758 {
1759 Tx.SetInt(cObj->x); Ty = cObj->y;
1760 int32_t iTx = Tx._getInt();
1761 if (!FindConSiteSpot(rx&: iTx, ry&: Ty, wdt: pDef->Shape.Wdt, hgt: pDef->Shape.Hgt, category: pDef->Category, hrange: 20))
1762 // No site found: fail
1763 {
1764 Finish(); return;
1765 }
1766 Tx.SetInt(iTx);
1767 }
1768
1769 // command has been validated: check for script overload now
1770 int32_t scriptresult = cObj->Call(PSF_ControlCommandConstruction, pPars: {C4VObj(pObj: Target), Tx, C4VInt(iVal: Ty), C4VObj(pObj: Target2), C4VID(idVal: Data)}).getInt();
1771 // script call might have deleted object
1772 if (!cObj->Status) return;
1773 if (1 == scriptresult) return;
1774 if (2 == scriptresult)
1775 {
1776 Finish(fSuccess: true); return;
1777 }
1778 if (3 == scriptresult)
1779 {
1780 Finish(); return;
1781 }
1782
1783 // Has no construction kit: acquire one
1784 C4Object *pKit;
1785 if (!(pKit = cObj->Contents.Find(id: C4ID_Conkit)))
1786 {
1787 cObj->AddCommand(iCommand: C4CMD_Acquire, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50, pTarget2: nullptr, fInitEvaluation: true, iData: C4ID_Conkit, fAppend: false, iRetries: 5, szText: nullptr, iBaseMode: C4CMD_Mode_Sub); return;
1788 }
1789
1790 // Move to construction site
1791 if (!Inside<int32_t>(ival: cObj->x - Tx._getInt(), lbound: -iMoveToRange, rbound: +iMoveToRange)
1792 || !Inside<int32_t>(ival: cObj->y - Ty, lbound: -20, rbound: +20))
1793 {
1794 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Tx, iTy: Ty, iUpdateInterval: 50); return;
1795 }
1796
1797 // Check construction site
1798 if (!ConstructionCheck(id: Data, iX: Tx._getInt(), iY: Ty, pByObj: cObj))
1799 // Site no good: fail
1800 {
1801 Finish(); return;
1802 }
1803
1804 // Create construction
1805 C4Object *pConstruction = Game.CreateObjectConstruction(type: Data, pCreator: nullptr, owner: cObj->Owner, ctx: Tx._getInt(), bty: Ty, con: 1, terrain: true);
1806
1807 // Remove conkit
1808 pKit->AssignRemoval();
1809
1810 // Finish, start building
1811 Finish(fSuccess: true);
1812 cObj->AddCommand(iCommand: C4CMD_Build, pTarget: pConstruction);
1813}
1814
1815bool C4Command::FlightControl() // Called by DFA_WALK, DFA_FLIGHT
1816{
1817 // Objects with CanFly physical only
1818 if (!cObj->GetPhysical()->CanFly) return false;
1819 // Crew members or pathfinder objects only
1820 if (!((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)) return false;
1821
1822 // Not while in a disabled action
1823 if (cObj->Action.Act > ActIdle)
1824 {
1825 C4ActionDef *actdef = &(cObj->Def->ActMap[cObj->Action.Act]);
1826 if (actdef->Disabled) return false;
1827 }
1828
1829 // Target angle
1830 int32_t cx = cObj->x, cy = cObj->y;
1831 int32_t iAngle = Angle(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty); while (iAngle > 180) iAngle -= 360;
1832
1833 // Target in flight angle (sector +/- from straight up), beyond minimum distance, and top free
1834 if (Inside(ival: iAngle, lbound: -FlightAngleRange, rbound: +FlightAngleRange)
1835 || Inside(ival: iAngle, lbound: -FlightAngleRange, rbound: +FlightAngleRange))
1836 if (Distance(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty) > 30)
1837 {
1838 int32_t iTopFree;
1839 for (iTopFree = 0; (iTopFree < 50) && !GBackSolid(x: cx, y: cy + cObj->Shape.y - iTopFree); ++iTopFree);
1840 if (iTopFree >= 15)
1841 {
1842 // Take off
1843 cObj->SetActionByName(szActName: "Fly"); // This is a little primitive... we should have a ObjectActionFly or maybe a command for this...
1844 }
1845 }
1846
1847 // No flight control
1848 return false;
1849}
1850
1851bool C4Command::JumpControl() // Called by DFA_WALK
1852{
1853 // Crew members or pathfinder objects only
1854 if (!((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)) return false;
1855
1856 // Target angle
1857 int32_t cx = cObj->x, cy = cObj->y;
1858 int32_t iAngle = Angle(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty); while (iAngle > 180) iAngle -= 360;
1859
1860 // Diagonal free jump (if in angle range, minimum distance, and top free)
1861 if (Inside(ival: iAngle - JumpAngle, lbound: -JumpAngleRange, rbound: +JumpAngleRange)
1862 || Inside(ival: iAngle + JumpAngle, lbound: -JumpAngleRange, rbound: +JumpAngleRange))
1863 if (PathFree(x1: cx, y1: cy, x2: Tx._getInt(), y2: Ty))
1864 if (Distance(iX1: cx, iY1: cy, iX2: Tx._getInt(), iY2: Ty) > 30)
1865 {
1866 int32_t iTopFree;
1867 for (iTopFree = 0; (iTopFree < 50) && !GBackSolid(x: cx, y: cy + cObj->Shape.y - iTopFree); ++iTopFree);
1868 if (iTopFree >= 15)
1869 {
1870 cObj->AddCommand(iCommand: C4CMD_Jump, pTarget: nullptr, iTx: Tx, iTy: Ty); return true;
1871 }
1872 }
1873
1874 // High angle side move - jump (2x range)
1875 if (Inside<int32_t>(ival: iAngle - JumpHighAngle, lbound: -3 * JumpAngleRange, rbound: +3 * JumpAngleRange))
1876 // Vertical range
1877 if (Inside<int32_t>(ival: cy - Ty, lbound: 10, rbound: 40))
1878 {
1879 int32_t iSide = SolidOnWhichSide(iX: Tx._getInt(), iY: Ty); // take jump height of side move position into consideration...!
1880 int32_t iDist = 5 * Abs(val: cy - Ty) / 6;
1881 int32_t iSideX = cx - iDist * iSide, iSideY = cy; AdjustMoveToTarget(rX&: iSideX, rY&: iSideY, fFreeMove: false, iShapeHgt: 0);
1882 // Side move target in range
1883 if (Inside<int32_t>(ival: iSideY - cy, lbound: -20, rbound: +20))
1884 {
1885 // Path free from side move target to jump target
1886 if (PathFree(x1: iSideX, y1: iSideY, x2: Tx._getInt(), y2: Ty))
1887 {
1888 cObj->AddCommand(iCommand: C4CMD_Jump, pTarget: nullptr, iTx: Tx, iTy: Ty);
1889 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: iSideX, iTy: iSideY, iUpdateInterval: 50);
1890 return true;
1891 }
1892 }
1893 }
1894
1895 // Low side contact jump
1896 int32_t iLowSideRange = 5;
1897 if (cObj->t_contact & CNAT_Right)
1898 if (Inside(ival: iAngle - JumpLowAngle, lbound: -iLowSideRange * JumpAngleRange, rbound: +iLowSideRange * JumpAngleRange))
1899 {
1900 cObj->AddCommand(iCommand: C4CMD_Jump, pTarget: nullptr, iTx: Tx, iTy: Ty); return true;
1901 }
1902 if (cObj->t_contact & CNAT_Left)
1903 if (Inside(ival: iAngle + JumpLowAngle, lbound: -iLowSideRange * JumpAngleRange, rbound: +iLowSideRange * JumpAngleRange))
1904 {
1905 cObj->AddCommand(iCommand: C4CMD_Jump, pTarget: nullptr, iTx: Tx, iTy: Ty); return true;
1906 }
1907
1908 // No jump control
1909 return false;
1910}
1911
1912void C4Command::Transfer()
1913{
1914 // No target: failure
1915 if (!Target) { Finish(); return; }
1916
1917 // Find transfer zone
1918 C4TransferZone *pZone;
1919 int32_t iEntryX, iEntryY;
1920 if (!(pZone = Game.TransferZones.Find(pObj: Target))) { Finish(); return; }
1921
1922 // Not at or in transfer zone: move to entry point
1923 if (!Inside<int32_t>(ival: cObj->x - pZone->X, lbound: -5, rbound: pZone->Wdt - 1 + 5))
1924 {
1925 if (!pZone->GetEntryPoint(rX&: iEntryX, rY&: iEntryY, iToX: cObj->x, iToY: cObj->y)) { Finish(); return; }
1926 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: iEntryX, iTy: iEntryY, iUpdateInterval: 25);
1927 return;
1928 }
1929
1930 // Call target transfer script
1931 if (!Tick5)
1932 {
1933 C4AulScriptFunc *f;
1934 bool fHandled = (f = Target->Def->Script.SFn_ControlTransfer) != nullptr;
1935 if (fHandled) fHandled = f->Exec(pObj: Target, pPars: {C4VObj(pObj: cObj), Tx, C4VInt(iVal: Ty)}).getBool();
1936
1937 if (!fHandled)
1938 // Transfer not handled by target: done
1939 {
1940 Finish(fSuccess: true); return;
1941 }
1942 }
1943}
1944
1945void C4Command::Attack()
1946{
1947 // No target: failure
1948 if (!Target) { Finish(); return; }
1949
1950 // Target is crew member
1951 if (Target->OCF & OCF_CrewMember)
1952 {
1953 C4Object *pProjectile = nullptr;
1954 // Throw projectile at target
1955 for (C4ObjectLink *pLnk = cObj->Contents.First; pLnk && (pProjectile = pLnk->Obj); pLnk = pLnk->Next)
1956 if (pProjectile->Def->Projectile)
1957 {
1958 // Add throw command
1959 cObj->AddCommand(iCommand: C4CMD_Throw, pTarget: pProjectile, iTx: Target->x, iTy: Target->y, iUpdateInterval: 2);
1960 return;
1961 }
1962
1963 // Follow containment
1964 if (cObj->Contained != Target->Contained)
1965 {
1966 // Exit
1967 if (cObj->Contained) cObj->AddCommand(iCommand: C4CMD_Exit, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 10);
1968 // Enter
1969 else cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target->Contained, iTx: 0, iTy: 0, iUpdateInterval: 10);
1970 return;
1971 }
1972
1973 // Move to target
1974 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: nullptr, iTx: Target->x, iTy: Target->y, iUpdateInterval: 10);
1975 return;
1976 }
1977
1978 // For now, attack crew members only
1979 else
1980 {
1981 // Success, target might be no crew member due to that is has been killed
1982 Finish(fSuccess: true);
1983 return;
1984 }
1985}
1986
1987void C4Command::Buy()
1988{
1989 // Base buying disabled? Fail.
1990 if (~Game.C4S.Game.Realism.BaseFunctionality & BASEFUNC_Buy) { Finish(); return; }
1991 // No target (base) object specified: find closest base
1992 int32_t cnt; C4Object *pBase;
1993 if (!Target)
1994 for (cnt = 0; pBase = Game.FindFriendlyBase(iPlayer: cObj->Owner, iIndex: cnt); cnt++)
1995 if (!Target || Distance(iX1: cObj->x, iY1: cObj->y, iX2: pBase->x, iY2: pBase->y) < Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target->x, iY2: Target->y))
1996 Target = pBase;
1997 // No target (base) object: fail
1998 if (!Target) { Finish(); return; }
1999 // No type to buy specified: open buy menu for base
2000 if (!Data)
2001 {
2002 cObj->ActivateMenu(iMenu: C4MN_Buy, iMenuSelect: 0, iMenuData: 0, iMenuPosition: 0, pTarget: Target);
2003 Finish(fSuccess: true); return;
2004 }
2005 // Target object is no base or hostile: fail
2006 if (!ValidPlr(plr: Target->Base) || Hostile(plr1: Target->Base, plr2: cObj->Owner))
2007 {
2008 Finish(); return;
2009 }
2010 // Target material undefined: fail
2011 C4Def *pDef = C4Id2Def(id: Data);
2012 if (!pDef) { Finish(); return; }
2013 // Material not available for purchase at base: fail
2014 if (Game.Players.Get(iPlayer: Target->Base)->HomeBaseMaterial.GetIDCount(id: Data) <= 0)
2015 {
2016 Finish(fSuccess: false, szFailMessage: LoadResStr(id: C4ResStrTableKey::IDS_PLR_NOTAVAIL, args: pDef->GetName()).c_str());
2017 return;
2018 }
2019 // Base owner has not enough funds: fail
2020 if (Game.Players.Get(iPlayer: Target->Base)->Wealth < pDef->GetValue(pInBase: Target, iBuyPlayer: cObj->Owner))
2021 {
2022 Finish(fSuccess: false, szFailMessage: LoadResStr(id: C4ResStrTableKey::IDS_PLR_NOWEALTH)); return;
2023 }
2024 // Not within target object: enter
2025 if (cObj->Contained != Target)
2026 {
2027 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
2028 }
2029 // Buy object(s)
2030 for (Tx.SetInt(std::max<int32_t>(a: Tx._getInt(), b: 1)); Tx._getInt(); Tx--)
2031 if (!Buy2Base(iPlr: cObj->Owner, pBase: Target, id: Data))
2032 // Failed (with ugly message)
2033 {
2034 Finish(); return;
2035 }
2036 // Done: success
2037 Finish(fSuccess: true);
2038}
2039
2040void C4Command::Sell()
2041{
2042 // Base sale disabled? Fail.
2043 if (~Game.C4S.Game.Realism.BaseFunctionality & BASEFUNC_Sell) { Finish(); return; }
2044 // No target (base) object specified: find closest base
2045 int32_t cnt; C4Object *pBase;
2046 if (!Target)
2047 for (cnt = 0; pBase = Game.FindBase(iPlayer: cObj->Owner, iIndex: cnt); cnt++)
2048 if (!Target || Distance(iX1: cObj->x, iY1: cObj->y, iX2: pBase->x, iY2: pBase->y) < Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target->x, iY2: Target->y))
2049 Target = pBase;
2050 // No target (base) object: fail
2051 if (!Target) { Finish(); return; }
2052 // No type to sell specified: open sell menu for base
2053 if (!Data)
2054 {
2055 cObj->ActivateMenu(iMenu: C4MN_Sell, iMenuSelect: 0, iMenuData: 0, iMenuPosition: 0, pTarget: Target);
2056 Finish(fSuccess: true); return;
2057 }
2058 // Target object is no base or hostile: fail
2059 if (!ValidPlr(plr: Target->Base) || Hostile(plr1: Target->Base, plr2: cObj->Owner))
2060 {
2061 Finish(); return;
2062 }
2063 // Not within target object: enter
2064 if (cObj->Contained != Target)
2065 {
2066 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
2067 }
2068 // Sell object(s)
2069 for (Tx.SetInt(std::max<int32_t>(a: Tx._getInt(), b: 1)); Tx._getInt(); Tx--)
2070 if (!SellFromBase(iPlr: cObj->Owner, pBase: Target, id: Data, pSellObj: Target2))
2071 // Failed
2072 {
2073 Finish(); return;
2074 }
2075 else
2076 // preferred sell object can be sold once only :)
2077 Target2 = nullptr;
2078 // Done
2079 Finish(fSuccess: true);
2080}
2081
2082void C4Command::Acquire()
2083{
2084 // No type to acquire specified: fail
2085 if (!Data) { Finish(); return; }
2086
2087 // Target material in inventory: done
2088 if (cObj->Contents.Find(id: Data))
2089 {
2090 Finish(fSuccess: true); return;
2091 }
2092
2093 // script overload
2094 int32_t scriptresult = cObj->Call(PSF_ControlCommandAcquire, pPars: {C4VObj(pObj: Target), Tx, C4VInt(iVal: Ty), C4VObj(pObj: Target2), C4VID(idVal: Data)}).getInt();
2095
2096 // script call might have deleted object
2097 if (!cObj->Status) return;
2098 if (1 == scriptresult) return;
2099 if (2 == scriptresult)
2100 {
2101 Finish(fSuccess: true); return;
2102 }
2103 if (3 == scriptresult)
2104 {
2105 Finish(); return;
2106 }
2107
2108 // Find available material
2109 C4Object *pMaterial = nullptr;
2110 // Next closest
2111 while (pMaterial = Game.FindObject(id: Data, iX: cObj->x, iY: cObj->y, iWdt: -1, iHgt: -1, ocf: OCF_Available, szAction: nullptr, pActionTarget: nullptr, pExclude: nullptr, pContainer: nullptr, iOwner: ANY_OWNER, pFindNext: pMaterial))
2112 // Object is not in container to be ignored
2113 if (!Target2 || pMaterial->Contained != Target2)
2114 // Object is near enough
2115 if (Inside<C4ValueInt>(ival: cObj->x - pMaterial->x, lbound: -Tx._getInt(), rbound: +Tx._getInt()))
2116 if (Inside(ival: cObj->y - pMaterial->y, lbound: -Ty, rbound: +Ty))
2117 // Object is not connected to a pipe (for line construction kits)
2118 if (!Game.FindObject(id: C4ID_SourcePipe, iX: 0, iY: 0, iWdt: 0, iHgt: 0, ocf: OCF_All, szAction: "Connect", pActionTarget: pMaterial))
2119 if (!Game.FindObject(id: C4ID_DrainPipe, iX: 0, iY: 0, iWdt: 0, iHgt: 0, ocf: OCF_All, szAction: "Connect", pActionTarget: pMaterial))
2120 // Must be complete
2121 if (pMaterial->OCF & OCF_FullCon)
2122 // Doesn't burn
2123 if (!pMaterial->GetOnFire())
2124 // We found one
2125 break;
2126
2127 // Available material found: get material
2128 if (pMaterial)
2129 {
2130 cObj->AddCommand(iCommand: C4CMD_Get, pTarget: pMaterial, iTx: 0, iTy: 0, iUpdateInterval: 40); return;
2131 }
2132
2133 // No available material found: buy material
2134 // This command will fail immediately if buying at bases is not possible
2135 // - but the command should be created anyway because it might be overloaded
2136 cObj->AddCommand(iCommand: C4CMD_Buy, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 100, pTarget2: nullptr, fInitEvaluation: true, iData: Data, fAppend: false, iRetries: 0, szText: nullptr, iBaseMode: C4CMD_Mode_Sub);
2137}
2138
2139void C4Command::Fail(const char *szFailMessage)
2140{
2141 // Check for base command (next unfinished)
2142 C4Command *pBase = GetBaseCommand();
2143
2144 bool ExecFail = false;
2145 switch (BaseMode)
2146 {
2147 // silent subcommand
2148 case C4CMD_Mode_SilentSub:
2149 {
2150 // Just count failures in base command
2151 if (pBase) { pBase->Failures++; return; }
2152 else ExecFail = true;
2153 break;
2154 }
2155 // verbose subcommand
2156 case C4CMD_Mode_Sub:
2157 {
2158 // Count failures in base command
2159 if (pBase) { pBase->Failures++; }
2160 // Execute fail message, if base will fail
2161 if (!pBase || !pBase->Retries) ExecFail = true;
2162 break;
2163 }
2164 // base command
2165 case C4CMD_Mode_Base:
2166 {
2167 // Just execute fail notice
2168 ExecFail = true;
2169 break;
2170 }
2171 // silent base command: do nothing
2172 }
2173
2174 char szCommandName[24 + 1];
2175 char szObjectName[C4MaxName + 1];
2176
2177 if (ExecFail && cObj && (cObj->OCF & OCF_CrewMember))
2178 {
2179 std::string failMessage;
2180 // Fail message
2181 if (szFailMessage) failMessage = szFailMessage;
2182 C4Object *l_Obj = cObj;
2183 switch (Command)
2184 {
2185 case C4CMD_Build:
2186 // Needed components
2187 if (!Target) break;
2188 // BuildNeedsMaterial call to builder script...
2189 if (cObj->Call(PSF_BuildNeedsMaterial, pPars: {
2190 C4VID(idVal: Target->Component.GetID(index: 0)), C4VInt(iVal: Target->Component.GetCount(index: 0))})) // WTF? This is passing current components. Not needed ones!
2191 break; // no message
2192 if (szFailMessage) break;
2193 failMessage = Target->GetNeededMatStr(pBuilder: cObj);
2194 break;
2195 case C4CMD_Call:
2196 {
2197 // Call fail-function in target object (no message if non-zero)
2198 int32_t l_Command = Command;
2199 if (CallFailed()) return;
2200 // Fail-function not available or returned zero: standard message
2201 SCopy(szSource: LoadCommandNameResStr(command: l_Command), sTarget: szCommandName);
2202 failMessage = LoadResStr(id: C4ResStrTableKey::IDS_CON_FAILURE, args&: szCommandName).c_str();
2203 break;
2204 }
2205 case C4CMD_Exit:
2206 // No message
2207 break;
2208 case C4CMD_Dig:
2209 // No message
2210 break;
2211 case C4CMD_Acquire:
2212 case C4CMD_Construct:
2213 // Already has a fail message
2214 if (szFailMessage) break;
2215 // Fail message with name of target type
2216 SCopy(szSource: LoadCommandNameResStr(command: Command), sTarget: szCommandName);
2217 C4Def *pDef; pDef = Game.Defs.ID2Def(id: Data);
2218 SCopy(szSource: pDef ? pDef->GetName() : LoadResStr(id: C4ResStrTableKey::IDS_OBJ_UNKNOWN), sTarget: szObjectName, iMaxL: C4MaxName);
2219 failMessage = LoadResStr(id: C4ResStrTableKey::IDS_CON_FAILUREOF, args&: szCommandName, args&: szObjectName).c_str();
2220 break;
2221 default:
2222 // Already has a fail message
2223 if (szFailMessage) break;
2224 // Standard no-can-do message
2225 SCopy(szSource: LoadCommandNameResStr(command: Command), sTarget: szCommandName);
2226 failMessage = LoadResStr(id: C4ResStrTableKey::IDS_CON_FAILURE, args&: szCommandName).c_str();
2227 break;
2228 }
2229 if (l_Obj) if (l_Obj->Status && !l_Obj->Def->SilentCommands)
2230 {
2231 // Message (if not empty)
2232 if (!failMessage.empty())
2233 {
2234 Game.Messages.Append(iType: C4GM_Target, szText: failMessage.c_str(), pTarget: l_Obj, iPlayer: NO_OWNER, iX: 0, iY: 0, bCol: FWhite, fNoDuplicates: true);
2235 }
2236 // Fail sound
2237 StartSoundEffect(name: "CommandFailure*", loop: false, volume: 100, obj: l_Obj);
2238 // Stop Clonk
2239 l_Obj->Action.ComDir = COMD_Stop;
2240 }
2241 }
2242}
2243
2244C4Object *CreateLine(C4ID idType, int32_t iOwner, C4Object *pFrom, C4Object *pTo);
2245
2246void C4Command::Energy()
2247{
2248 uint32_t ocf = OCF_All;
2249 // No target: fail
2250 if (!Target) { Finish(); return; }
2251 // Target can't be supplied: fail
2252 if (!(Target->Def->LineConnect & C4D_Power_Input)) { Finish(); return; }
2253 // Target supplied
2254 if (!(Game.Rules & C4RULE_StructuresNeedEnergy)
2255 || (Game.FindObject(id: C4ID_PowerLine, iX: 0, iY: 0, iWdt: 0, iHgt: 0, ocf: OCF_All, szAction: "Connect", pActionTarget: Target) && !Target->NeedEnergy))
2256 {
2257 Finish(fSuccess: true); return;
2258 }
2259 // No energy supply specified: find one
2260 if (!Target2) Target2 = Game.FindObject(id: 0, iX: Target->x, iY: Target->y, iWdt: -1, iHgt: -1, ocf: OCF_PowerSupply, szAction: nullptr, pActionTarget: nullptr, pExclude: Target);
2261 // No energy supply: fail
2262 if (!Target2) { Finish(); return; }
2263 // Energy supply too far away: fail
2264 if (Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target2->x, iY2: Target2->y) > 650) { Finish(); return; }
2265 // Not a valid energy supply: fail
2266 if (!(Target2->Def->LineConnect & C4D_Power_Output)) { Finish(); return; }
2267 // No linekit: get one
2268 C4Object *pKit, *pLine = nullptr, *pKitWithLine;
2269 if (!(pKit = cObj->Contents.Find(id: C4ID_Linekit)))
2270 {
2271 cObj->AddCommand(iCommand: C4CMD_Acquire, pTarget: nullptr, iTx: 0, iTy: 0, iUpdateInterval: 50, pTarget2: nullptr, fInitEvaluation: true, iData: C4ID_Linekit); return;
2272 }
2273 // Find line constructing kit
2274 for (int32_t cnt = 0; pKitWithLine = cObj->Contents.GetObject(Index: cnt); cnt++)
2275 if ((pKitWithLine->id == C4ID_Linekit) && (pLine = Game.FindObject(id: C4ID_PowerLine, iX: 0, iY: 0, iWdt: 0, iHgt: 0, ocf: OCF_All, szAction: "Connect", pActionTarget: pKitWithLine)))
2276 break;
2277 // No line constructed yet
2278 if (!pLine)
2279 {
2280 // Move to supply
2281 if (!Target2->At(ctx: cObj->x, cty: cObj->y, ocf))
2282 {
2283 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: Target2, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
2284 }
2285 // At power supply: connect
2286 pLine = CreateLine(idType: C4ID_PowerLine, iOwner: cObj->Owner, pFrom: Target2, pTo: pKit);
2287 if (!pLine) { Finish(); return; }
2288 StartSoundEffect(name: "Connect", loop: false, volume: 100, obj: cObj);
2289 return;
2290 }
2291 else
2292 {
2293 // A line is already present: Make sure not to override the target
2294 if (pLine->Action.Target == pKitWithLine)
2295 Target2 = pLine->Action.Target2;
2296 else
2297 Target2 = pLine->Action.Target;
2298 }
2299 // Move to target
2300 if (!Target->At(ctx: cObj->x, cty: cObj->y, ocf))
2301 {
2302 cObj->AddCommand(iCommand: C4CMD_MoveTo, pTarget: Target, iTx: 0, iTy: 0, iUpdateInterval: 50); return;
2303 }
2304 // Connect
2305 pLine->SetActionByName(szActName: "Connect", pTarget: Target2, pTarget2: Target);
2306 pKitWithLine->AssignRemoval();
2307 StartSoundEffect(name: "Connect", loop: 0, volume: 100, obj: cObj);
2308 // Done
2309 cObj->Action.ComDir = COMD_Stop;
2310 // Success
2311 Finish(fSuccess: true);
2312}
2313
2314void C4Command::Retry() {}
2315
2316void C4Command::Home()
2317{
2318 // At home base: done
2319 if (cObj->Contained && (cObj->Contained->Base == cObj->Owner))
2320 {
2321 Finish(fSuccess: true); return;
2322 }
2323 // No target (base) object specified: find closest base
2324 int32_t cnt; C4Object *pBase;
2325 if (!Target)
2326 for (cnt = 0; pBase = Game.FindBase(iPlayer: cObj->Owner, iIndex: cnt); cnt++)
2327 if (!Target || Distance(iX1: cObj->x, iY1: cObj->y, iX2: pBase->x, iY2: pBase->y) < Distance(iX1: cObj->x, iY1: cObj->y, iX2: Target->x, iY2: Target->y))
2328 Target = pBase;
2329 // No base: fail
2330 if (!Target) { Finish(); return; }
2331 // Enter base
2332 cObj->AddCommand(iCommand: C4CMD_Enter, pTarget: Target);
2333}
2334
2335void C4Command::Set(int32_t iCommand, C4Object *pObj, C4Object *pTarget, C4Value nTx, int32_t iTy,
2336 C4Object *pTarget2, int32_t iData, int32_t iUpdateInterval,
2337 bool fEvaluated, int32_t iRetries, const char *szText, int32_t iBaseMode)
2338{
2339 // Reset
2340 Clear(); Default();
2341 // Set
2342 Command = iCommand;
2343 cObj = pObj;
2344 Target = pTarget;
2345 Tx = nTx; Ty = iTy;
2346 Target2 = pTarget2;
2347 Data = iData;
2348 UpdateInterval = iUpdateInterval;
2349 Evaluated = fEvaluated;
2350 Retries = iRetries;
2351 if (szText) Text = szText;
2352 BaseMode = iBaseMode;
2353}
2354
2355void C4Command::Call()
2356{
2357 // No function name: fail
2358 if (Text.empty()) { Finish(); return; }
2359 // No target object: fail
2360 if (!Target) { Finish(); return; }
2361 // Done: success
2362 Finish(fSuccess: true);
2363 // Object call
2364 Target->Call(szFunctionCall: Text.c_str(), pPars: {C4VObj(pObj: cObj), Tx, C4VInt(iVal: Ty), C4VObj(pObj: Target2)});
2365 // Extreme caution notice: the script call might do just about anything
2366 // including clearing all commands (including this) i.e. through a call
2367 // to SetCommand. Thus, we must not do anything in this command anymore
2368 // after the script call (even the Finish has to be called before).
2369 // The Finish call being misled to the freshly created Build command (by
2370 // chance, the this-pointer was simply crap at the time) was reason for
2371 // the latest sync losses in 4.62.
2372}
2373
2374void C4Command::CompileFunc(StdCompiler *pComp)
2375{
2376 // Version
2377 int32_t iVersion = 0;
2378 if (pComp->Separator(eSep: StdCompiler::SEP_DOLLAR))
2379 {
2380 iVersion = 2;
2381 pComp->Value(rStruct: mkIntPackAdapt(rVal&: iVersion));
2382 pComp->Separator(eSep: StdCompiler::SEP_SEP);
2383 }
2384 else
2385 pComp->NoSeparator();
2386 // Command name
2387 pComp->Value(rStruct: mkEnumAdaptT<uint8_t>(rVal&: Command, pNames: EnumAdaptCommandEntries));
2388 pComp->Separator(eSep: StdCompiler::SEP_SEP);
2389 // Target X/Y
2390 pComp->Value(rStruct&: Tx); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2391 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Ty)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2392 // Target
2393 pComp->Value(rStruct: mkParAdapt(rObj&: Target, rPar: true)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2394 pComp->Value(rStruct: mkParAdapt(rObj&: Target2, rPar: true)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2395 // Data
2396 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Data)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2397 // Update interval
2398 pComp->Value(rStruct: mkIntPackAdapt(rVal&: UpdateInterval)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2399 // Flags
2400 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Evaluated)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2401 pComp->Value(rStruct: mkIntPackAdapt(rVal&: PathChecked)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2402 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Finished)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2403 // Retries
2404 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Failures)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2405 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Retries)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2406 pComp->Value(rStruct: mkIntPackAdapt(rVal&: Permit)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2407 // Base mode
2408 if (iVersion > 0)
2409 {
2410 pComp->Value(rStruct: mkIntPackAdapt(rVal&: BaseMode)); pComp->Separator(eSep: StdCompiler::SEP_SEP);
2411 }
2412 // Text
2413 pComp->Value(rString&: Text, eRawType: StdCompiler::RCT_All);
2414 if (iVersion < 2 && Text == "0") Text.clear();
2415}
2416
2417void C4Command::DenumeratePointers()
2418{
2419 DenumerateObjectPtrs(args&: Target, args&: Target2);
2420 Tx.DenumeratePointer();
2421}
2422
2423void C4Command::EnumeratePointers()
2424{
2425 EnumerateObjectPtrs(args&: Target, args&: Target2);
2426}
2427
2428int32_t C4Command::CallFailed()
2429{
2430 // No function name or no target object: cannot call fail-function
2431 if (Text.empty() || !Target) return 0;
2432 // Call failed-function
2433 return Target->Call(szFunctionCall: std::format(fmt: "~{}Failed", args&: Text).c_str(),
2434 pPars: {C4VObj(pObj: cObj), Tx, C4VInt(iVal: Ty), C4VObj(pObj: Target2)})._getInt();
2435 // Extreme caution notice: the script call might do just about anything
2436 // including clearing all commands (including this) i.e. through a call
2437 // to SetCommand. Thus, we must not do anything in this command anymore
2438 // after the script call.
2439}
2440
2441int32_t C4Command::GetExpGain()
2442{
2443 // return exp gained by this command
2444 switch (Command)
2445 {
2446 // internal
2447 case C4CMD_Wait:
2448 case C4CMD_Transfer:
2449 case C4CMD_Retry:
2450 case C4CMD_Call:
2451 return 0;
2452
2453 // regular move commands
2454 case C4CMD_Follow:
2455 case C4CMD_MoveTo:
2456 case C4CMD_Enter:
2457 case C4CMD_Exit:
2458 return 1;
2459
2460 // simple activities
2461 case C4CMD_Grab:
2462 case C4CMD_UnGrab:
2463 case C4CMD_Throw:
2464 case C4CMD_Jump:
2465 case C4CMD_Get:
2466 case C4CMD_Put:
2467 case C4CMD_Drop:
2468 case C4CMD_Activate:
2469 case C4CMD_PushTo:
2470 case C4CMD_Dig:
2471 case C4CMD_Context:
2472 case C4CMD_Buy:
2473 case C4CMD_Sell:
2474 case C4CMD_Take:
2475 case C4CMD_Take2:
2476 return 1;
2477
2478 // not that simple
2479 case C4CMD_Acquire:
2480 case C4CMD_Home:
2481 return 2;
2482
2483 // advanced activities
2484 case C4CMD_Chop:
2485 case C4CMD_Build:
2486 case C4CMD_Construct:
2487 case C4CMD_Energy:
2488 return 5;
2489
2490 // victory!
2491 case C4CMD_Attack:
2492 return 15;
2493 }
2494 // unknown command
2495 return 0;
2496}
2497
2498C4Command *C4Command::GetBaseCommand() const
2499{
2500 for (auto command = Next; command; command = command->Next)
2501 {
2502 if (!command->Finished)
2503 {
2504 return command;
2505 }
2506 }
2507 return nullptr;
2508}
2509