1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, 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/* Move liquids in the landscape using individual transport spots */
18
19#include <C4Include.h>
20#include <C4MassMover.h>
21
22#include <C4Random.h>
23#include <C4Material.h>
24#include <C4Game.h>
25#include <C4Wrappers.h>
26
27// Note: creation optimized using advancing CreatePtr, so sequential
28// creation does not keep rescanning the complete set for a free
29// slot. (This had caused extreme delays.) This had the effect that
30// MMs created by another MM being executed were oftenly executed
31// within the same frame repetitiously leading to long distance mass
32// movement in no-time. To avoid this, set execution is done in
33// opposite direction. We now have have smoothly running masses in
34// a mathematical triangular shape with no delays! Since masses are
35// running slower and smoother, overall MM counts are much lower,
36// hardly ever exceeding 1000. October 1997
37
38C4MassMoverSet::C4MassMoverSet()
39{
40 Default();
41}
42
43C4MassMoverSet::~C4MassMoverSet()
44{
45 Clear();
46}
47
48void C4MassMoverSet::Clear() {}
49
50void C4MassMoverSet::Execute()
51{
52 C4MassMover *cmm;
53 // Init counts
54 Count = 0;
55 // Execute & count
56 for (int32_t speed = 2; speed > 0; speed--)
57 {
58 cmm = &(Set[C4MassMoverChunk - 1]);
59 for (int32_t cnt = 0; cnt < C4MassMoverChunk; cnt++, cmm--)
60 if (cmm->Mat != MNone)
61 {
62 Count++; cmm->Execute();
63 }
64 }
65}
66
67bool C4MassMoverSet::Create(int32_t x, int32_t y, bool fExecute)
68{
69 if (Count == C4MassMoverChunk) return false;
70#ifdef DEBUGREC
71 C4RCMassMover rc;
72 rc.x = x; rc.y = y;
73 AddDbgRec(RCT_MMC, &rc, sizeof(rc));
74#endif
75 int32_t cptr = CreatePtr;
76 do
77 {
78 cptr++;
79 if (cptr >= C4MassMoverChunk) cptr = 0;
80 if (Set[cptr].Mat == MNone)
81 {
82 if (!Set[cptr].Init(tx: x, ty: y)) return false;
83 CreatePtr = cptr;
84 if (fExecute) Set[cptr].Execute();
85 return true;
86 }
87 } while (cptr != CreatePtr);
88 return false;
89}
90
91bool C4MassMover::Init(int32_t tx, int32_t ty)
92{
93 // Out of bounds check
94 if (!Inside<int32_t>(ival: tx, lbound: 0, GBackWdt - 1) || !Inside<int32_t>(ival: ty, lbound: 0, GBackHgt - 1))
95 return false;
96 // Check mat
97 Mat = GBackMat(x: tx, y: ty);
98 x = tx; y = ty;
99 Game.MassMover.Count++;
100 return (Mat != MNone);
101}
102
103void C4MassMover::Cease()
104{
105#ifdef DEBUGREC
106 C4RCMassMover rc;
107 rc.x = x; rc.y = y;
108 AddDbgRec(RCT_MMD, &rc, sizeof(rc));
109#endif
110 Game.MassMover.Count--;
111 Mat = MNone;
112}
113
114bool C4MassMover::Execute()
115{
116 int32_t tx, ty;
117
118 // Lost target material
119 if (GBackMat(x, y) != Mat) { Cease(); return false; }
120
121 // Check for transfer target space
122 C4Material *pMat = Game.Material.Map + Mat;
123 tx = x; ty = y;
124 if (!Game.Landscape.FindMatPath(fx&: tx, fy&: ty, ydir: +1, mdens: pMat->Density, mslide: pMat->MaxSlide))
125 {
126 // Contact material reaction check: corrosion/evaporation/inflammation/etc.
127 if (Corrosion(dx: +0, dy: +1) || Corrosion(dx: -1, dy: +0) || Corrosion(dx: +1, dy: +0))
128 {
129 // material has been used up
130 Game.Landscape.ExtractMaterial(fx: x, fy: y);
131 return true;
132 }
133
134 // No space, die
135 Cease(); return false;
136 }
137
138 // Save back material that is about to be overwritten.
139 int omat;
140 if (Game.C4S.Game.Realism.LandscapeInsertThrust)
141 omat = GBackMat(x: tx, y: ty);
142
143 // Transfer mass
144 if (Random(iRange: 10))
145 SBackPix(x: tx, y: ty, npix: Mat2PixColDefault(mat: Game.Landscape.ExtractMaterial(fx: x, fy: y)) + GBackIFT(x: tx, y: ty));
146 else
147 Game.Landscape.InsertMaterial(mat: Game.Landscape.ExtractMaterial(fx: x, fy: y), tx, ty, vx: 0, vy: 1);
148
149 // Reinsert material (thrusted aside)
150 if (Game.C4S.Game.Realism.LandscapeInsertThrust && MatValid(mat: omat) && Game.Material.Map[omat].Density > 0)
151 Game.Landscape.InsertMaterial(mat: omat, tx, ty: ty + 1);
152
153 // Create new mover at target
154 Game.MassMover.Create(x: tx, y: ty, fExecute: !Rnd3());
155
156 return true;
157}
158
159bool C4MassMover::Corrosion(int32_t dx, int32_t dy)
160{
161 // check reaction map of massmover-mat to target mat
162 int32_t tmat = GBackMat(x: x + dx, y: y + dy);
163 C4MaterialReaction *pReact = Game.Material.GetReactionUnsafe(iPXSMat: Mat, iLandscapeMat: tmat);
164 if (pReact)
165 {
166 C4Fixed xdir = Fix0, ydir = Fix0;
167 if ((*pReact->pFunc)(pReact, x, y, x + dx, y + dy, xdir, ydir, Mat, tmat, meeMassMove, nullptr))
168 return true;
169 }
170 return false;
171}
172
173void C4MassMoverSet::Default()
174{
175 int32_t cnt;
176 for (cnt = 0; cnt < C4MassMoverChunk; cnt++) Set[cnt].Mat = MNone;
177 Count = 0;
178 CreatePtr = 0;
179}
180
181bool C4MassMoverSet::Save(C4Group &hGroup)
182{
183 int32_t cnt;
184 // Consolidate
185 Consolidate();
186 // Recount
187 Count = 0;
188 for (cnt = 0; cnt < C4MassMoverChunk; cnt++)
189 if (Set[cnt].Mat != MNone)
190 Count++;
191 // All empty: delete component
192 if (!Count)
193 {
194 hGroup.Delete(C4CFN_MassMover);
195 return true;
196 }
197 // Save set
198 if (!hGroup.Add(C4CFN_MassMover, pBuffer: Set, iSize: Count * sizeof(C4MassMover)))
199 return false;
200 // Success
201 return true;
202}
203
204bool C4MassMoverSet::Load(C4Group &hGroup)
205{
206 // clear previous
207 Clear(); Default();
208
209 size_t iBinSize, iMoverSize = sizeof(C4MassMover);
210 if (!hGroup.AccessEntry(C4CFN_MassMover, iSize: &iBinSize)) return false;
211 if ((iBinSize % iMoverSize) != 0) return false;
212
213 // load new
214 Count = iBinSize / iMoverSize;
215 if (!hGroup.Read(pBuffer: Set, iSize: iBinSize)) return false;
216 return true;
217}
218
219void C4MassMoverSet::Consolidate()
220{
221 // Consolidate set
222 int32_t iSpot, iPtr, iConsolidated;
223 for (iSpot = -1, iPtr = 0, iConsolidated = 0; iPtr < C4MassMoverChunk; iPtr++)
224 {
225 // Empty: set new spot if needed
226 if (Set[iPtr].Mat == MNone)
227 {
228 if (iSpot == -1) iSpot = iPtr;
229 }
230 // Full: move down to empty spot if possible
231 else if (iSpot != -1)
232 {
233 // Move to spot
234 Set[iSpot] = Set[iPtr];
235 Set[iPtr].Mat = MNone;
236 iConsolidated++;
237 // Advance empty spot (as far as ptr)
238 for (; iSpot < iPtr; iSpot++)
239 if (Set[iSpot].Mat == MNone)
240 break;
241 // No empty spot below ptr
242 if (iSpot == iPtr) iSpot = -1;
243 }
244 }
245 // Reset create ptr
246 CreatePtr = 0;
247}
248
249void C4MassMoverSet::Synchronize()
250{
251 Consolidate();
252}
253
254void C4MassMoverSet::Copy(C4MassMoverSet &rSet)
255{
256 Clear();
257 Count = rSet.Count;
258 CreatePtr = rSet.CreatePtr;
259 for (int32_t cnt = 0; cnt < C4MassMoverChunk; cnt++) Set[cnt] = rSet.Set[cnt];
260}
261