1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// complex dynamic landscape creator
19
20#include <C4Include.h>
21#include <C4MapCreatorS2.h>
22#include <C4Random.h>
23
24#include <C4Game.h>
25#include <C4Wrappers.h>
26
27#include <cassert>
28
29// C4MCCallbackArray
30
31C4MCCallbackArray::C4MCCallbackArray(C4AulFunc *pSFunc, C4MapCreatorS2 *pMapCreator)
32{
33 // store fn
34 pSF = pSFunc;
35 // zero fields
36 pMap = nullptr; pNext = nullptr;
37 // store and add in map creator
38 if (this->pMapCreator = pMapCreator)
39 pMapCreator->CallbackArrays.Add(pNewArray: this);
40 // done
41}
42
43C4MCCallbackArray::~C4MCCallbackArray()
44{
45 // clear map, if present
46 delete[] pMap;
47}
48
49void C4MCCallbackArray::EnablePixel(int32_t iX, int32_t iY)
50{
51 // array not yet created? then do that now!
52 if (!pMap)
53 {
54 // safety
55 if (!pMapCreator) return;
56 // get current map size
57 C4MCMap *pCurrMap = pMapCreator->pCurrentMap;
58 if (!pCurrMap) return;
59 iWdt = pCurrMap->Wdt; iHgt = pCurrMap->Hgt;
60 // create bitmap
61 int32_t iSize = (iWdt * iHgt + 7) / 8;
62 pMap = new uint8_t[iSize]{};
63 // done
64 }
65 // safety: do not set outside map!
66 if (iX < 0 || iY < 0 || iX >= iWdt || iY >= iHgt) return;
67 // set in map
68 int32_t iIndex = iX + iY * iWdt;
69 pMap[iIndex / 8] |= 1 << (iIndex % 8);
70 // done
71}
72
73void C4MCCallbackArray::Execute(int32_t iMapZoom)
74{
75 // safety
76 if (!pSF || !pMap) return;
77 // pre-create parset
78 C4AulParSet Pars(C4VInt(iVal: 0), C4VInt(iVal: 0), C4VInt(iVal: iMapZoom));
79 // call all funcs
80 int32_t iIndex = iWdt * iHgt;
81 while (iIndex--)
82 if (pMap[iIndex / 8] & (1 << (iIndex % 8)))
83 {
84 // set pars
85 Pars[0] = C4VInt(iVal: (iIndex % iWdt) * iMapZoom - (iMapZoom / 2));
86 Pars[1] = C4VInt(iVal: (iIndex / iWdt) * iMapZoom - (iMapZoom / 2));
87 // call
88 pSF->Exec(pObj: nullptr, pPars: Pars);
89 }
90 // done
91}
92
93// C4MCCallbackArrayList
94
95void C4MCCallbackArrayList::Add(C4MCCallbackArray *pNewArray)
96{
97 // add to end
98 if (pFirst)
99 {
100 C4MCCallbackArray *pLast = pFirst;
101 while (pLast->pNext) pLast = pLast->pNext;
102 pLast->pNext = pNewArray;
103 }
104 else pFirst = pNewArray;
105}
106
107void C4MCCallbackArrayList::Clear()
108{
109 // remove all arrays
110 C4MCCallbackArray *pArray, *pNext = pFirst;
111 while (pArray = pNext)
112 {
113 pNext = pArray->pNext;
114 delete pArray;
115 }
116 // zero first-field
117 pFirst = nullptr;
118}
119
120void C4MCCallbackArrayList::Execute(int32_t iMapZoom)
121{
122 // execute all arrays
123 for (C4MCCallbackArray *pArray = pFirst; pArray; pArray = pArray->pNext)
124 pArray->Execute(iMapZoom);
125}
126
127// C4MCNode
128
129C4MCNode::C4MCNode(C4MCNode *pOwner)
130{
131 // reg to owner
132 Reg2Owner(pOwner);
133 // no name
134 *Name = 0;
135}
136
137C4MCNode::C4MCNode(C4MCNode *pOwner, C4MCNode &rTemplate, bool fClone)
138{
139 // set owner and stuff
140 Reg2Owner(pOwner);
141 // copy children from template
142 for (C4MCNode *pChild = rTemplate.Child0; pChild; pChild = pChild->Next)
143 pChild->clone(pToNode: this);
144
145 // Preserve the name if pOwner is a MCN_Node, which is only the case if pOwner is a C4MapCreatorS2
146 if (pOwner && pOwner->Type() == MCN_Node)
147 SCopy(szSource: rTemplate.Name, sTarget: Name, iMaxL: C4MaxName);
148 else
149 *Name = 0; // Default behavior: reset the name
150}
151
152C4MCNode::~C4MCNode()
153{
154 // clear
155 Clear();
156 // remove from list
157 if (Prev) Prev->Next = Next; else if (Owner) Owner->Child0 = Next;
158 if (Next) Next->Prev = Prev; else if (Owner) Owner->ChildL = Prev;
159}
160
161void C4MCNode::Reg2Owner(C4MCNode *pOwner)
162{
163 // init list
164 Child0 = ChildL = nullptr;
165 // owner?
166 if (Owner = pOwner)
167 {
168 // link into it
169 if (Prev = Owner->ChildL)
170 Prev->Next = this;
171 else
172 Owner->Child0 = this;
173 Owner->ChildL = this;
174 MapCreator = pOwner->MapCreator;
175 }
176 else
177 {
178 Prev = nullptr;
179 MapCreator = nullptr;
180 }
181 // we're always last entry
182 Next = nullptr;
183}
184
185void C4MCNode::Clear()
186{
187 // delete all children; they'll unreg themselves
188 while (Child0) delete Child0;
189}
190
191C4MCOverlay *C4MCNode::OwnerOverlay()
192{
193 for (C4MCNode *pOwnr = Owner; pOwnr; pOwnr = pOwnr->Owner)
194 if (C4MCOverlay *pOwnrOvrl = pOwnr->Overlay())
195 return pOwnrOvrl;
196 // no overlay-owner
197 return nullptr;
198}
199
200C4MCNode *C4MCNode::GetNodeByName(const char *szName)
201{
202 // search local list (backwards: last node has highest priority)
203 for (C4MCNode *pChild = ChildL; pChild; pChild = pChild->Prev)
204 // name match?
205 if (SEqual(szStr1: pChild->Name, szStr2: szName))
206 // yeah, success!
207 return pChild;
208 // search owner, if present
209 if (Owner) return Owner->GetNodeByName(szName);
210 // nothing found
211 return nullptr;
212}
213
214bool C4MCNode::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
215{
216 // no fields in base class
217 return false;
218}
219
220int32_t C4MCNode::IntPar(C4MCParser *pParser, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
221{
222 // check if int32_t
223 if (ValType == MCT_INT || ValType == MCT_PERCENT || ValType == MCT_PX)
224 return iVal;
225 throw C4MCParserErr(pParser, C4MCErr_FieldValInvalid, szSVal);
226}
227
228const char *C4MCNode::StrPar(C4MCParser *pParser, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
229{
230 // check if identifier
231 if (ValType != MCT_IDTF)
232 throw C4MCParserErr(pParser, C4MCErr_FieldValInvalid, szSVal);
233 return szSVal;
234}
235
236#define IntPar IntPar(pParser, szSVal, iVal, ValType) // shortcut for checked int32_t param
237#define StrPar StrPar(pParser, szSVal, iVal, ValType) // shortcut for checked str param
238
239void C4MCNode::ReEvaluate()
240{
241 // evaluate ourselves
242 Evaluate();
243 // evaluate children
244 for (C4MCNode *pChild = Child0; pChild; pChild = pChild->Next)
245 pChild->ReEvaluate();
246}
247
248// overlay
249
250C4MCOverlay::C4MCOverlay(C4MCNode *pOwner) : C4MCNode(pOwner)
251{
252 // zero members
253 X = Y = Wdt = Hgt = OffX = OffY = 0;
254 Material = MNone;
255 *Texture = 0;
256 Op = MCT_NONE;
257 MatClr = 0;
258 Algorithm = nullptr;
259 Sub = false;
260 ZoomX = ZoomY = 0;
261 FixedSeed = Seed = 0;
262 Turbulence = Lambda = Rotate = 0;
263 Invert = LooseBounds = Group = Mask = false;
264 pEvaluateFunc = pDrawFunc = nullptr;
265}
266
267C4MCOverlay::C4MCOverlay(C4MCNode *pOwner, C4MCOverlay &rTemplate, bool fClone) : C4MCNode(pOwner, rTemplate, fClone)
268{
269 // copy fields
270 X = rTemplate.X; Y = rTemplate.Y; Wdt = rTemplate.Wdt; Hgt = rTemplate.Hgt;
271 RX = rTemplate.RX; RY = rTemplate.RY; RWdt = rTemplate.RWdt; RHgt = rTemplate.RHgt;
272 OffX = rTemplate.OffX; OffY = rTemplate.OffY; ROffX = rTemplate.ROffX; ROffY = rTemplate.ROffY;
273 Material = rTemplate.Material;
274 SCopy(szSource: rTemplate.Texture, sTarget: Texture, iMaxL: C4MaxName);
275 Algorithm = rTemplate.Algorithm;
276 Sub = rTemplate.Sub;
277 ZoomX = rTemplate.ZoomX; ZoomY = rTemplate.ZoomY;
278 MatClr = rTemplate.MatClr;
279 Seed = rTemplate.Seed;
280 Alpha = rTemplate.Alpha; Beta = rTemplate.Beta; Turbulence = rTemplate.Turbulence; Lambda = rTemplate.Lambda;
281 Rotate = rTemplate.Rotate;
282 Invert = rTemplate.Invert; LooseBounds = rTemplate.LooseBounds; Group = rTemplate.Group; Mask = rTemplate.Mask;
283 FixedSeed = rTemplate.FixedSeed;
284 pEvaluateFunc = rTemplate.pEvaluateFunc;
285 pDrawFunc = rTemplate.pDrawFunc;
286 // zero non-template-fields
287 if (fClone) Op = rTemplate.Op; else Op = MCT_NONE;
288}
289
290void C4MCOverlay::Default()
291{
292 // default algo
293 Algorithm = GetAlgo(C4MC_DefAlgo);
294 // no mat (sky) default
295 Material = MNone;
296 *Texture = 0;
297 // but if mat is set, assume it sub
298 Sub = true;
299 // full size
300 OffX = OffY = X = Y = 0;
301 ROffX.Set(value: 0, percent: true); ROffY.Set(value: 0, percent: true); RX.Set(value: 0, percent: true); RY.Set(value: 0, percent: true);
302 Wdt = Hgt = C4MC_SizeRes;
303 RWdt.Set(C4MC_SizeRes, percent: true); RHgt.Set(C4MC_SizeRes, percent: true);
304 // def zoom
305 ZoomX = ZoomY = C4MC_ZoomRes;
306 // def values
307 Alpha.Set(value: 0, percent: false); Beta.Set(value: 0, percent: false); Turbulence = Lambda = Rotate = 0; Invert = LooseBounds = Group = Mask = false;
308 FixedSeed = 0;
309 // script funcs
310 pEvaluateFunc = pDrawFunc = nullptr;
311}
312
313bool C4MCOverlay::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
314{
315 int32_t iMat; C4MCAlgorithm *pAlgo;
316 // inherited fields
317 if (C4MCNode::SetField(pParser, szField, szSVal, iVal, ValType)) return true;
318 // local fields
319 for (C4MCNodeAttr *pAttr = &C4MCOvrlMap[0]; *pAttr->Name; pAttr++)
320 if (SEqual(szStr1: szField, szStr2: pAttr->Name))
321 {
322 // store according to field type
323 switch (pAttr->Type)
324 {
325 case C4MCV_Integer:
326 // simply store
327 this->*(pAttr->integer) = IntPar;
328 break;
329 case C4MCV_Percent:
330 {
331 (this->*(pAttr->intBool)).Set(IntPar, percent: ValType == MCT_PERCENT || ValType == MCT_INT);
332 break;
333 }
334 case C4MCV_Pixels:
335 {
336 (this->*(pAttr->intBool)).Set(IntPar, percent: ValType == MCT_PERCENT);
337 break;
338 }
339 case C4MCV_Material:
340 // get material by string
341 iMat = MapCreator->MatMap->Get(StrPar);
342 // check validity
343 if (iMat == MNone) throw C4MCParserErr(pParser, C4MCErr_MatNotFound, StrPar);
344 // store
345 this->*(pAttr->integer) = iMat;
346 break;
347 case C4MCV_Texture:
348 // check validity
349 if (!MapCreator->TexMap->CheckTexture(StrPar))
350 throw C4MCParserErr(pParser, C4MCErr_TexNotFound, StrPar);
351 // store
352 SCopy(StrPar, sTarget: this->*(pAttr->texture), iMaxL: C4MaxName);
353 break;
354 case C4MCV_Algorithm:
355 // get algo
356 pAlgo = GetAlgo(StrPar);
357 // check validity
358 if (!pAlgo) throw C4MCParserErr(pParser, C4MCErr_AlgoNotFound, StrPar);
359 // store
360 this->*(pAttr->algorithm) = pAlgo;
361 break;
362 case C4MCV_Boolean:
363 // store whether value is not zero
364 this->*(pAttr->boolean) = IntPar != 0;
365 break;
366 case C4MCV_Zoom:
367 // store calculated zoom
368 this->*(pAttr->integer) = BoundBy<int32_t>(C4MC_ZoomRes - IntPar, lbound: 1, C4MC_ZoomRes * 2);
369 break;
370 case C4MCV_ScriptFunc:
371 {
372 // get script func of main script
373 C4AulFunc *pSFunc = Game.Script.GetSFunc(StrPar, AccNeeded: AA_PROTECTED);
374 if (!pSFunc) throw C4MCParserErr(pParser, C4MCErr_SFuncNotFound, StrPar);
375 // add to main
376 this->*(pAttr->scriptFunc) = new C4MCCallbackArray(pSFunc, MapCreator);
377 break;
378 }
379 case C4MCV_None:
380 assert(!"C4MCNodeAttr of type C4MCV_None");
381 break;
382 }
383 // done
384 return true;
385 }
386 // nothing found :(
387 return false;
388}
389
390C4MCAlgorithm *C4MCOverlay::GetAlgo(const char *szName)
391{
392 // search map
393 for (C4MCAlgorithm *pAlgo = &C4MCAlgoMap[0]; pAlgo->Function; pAlgo++)
394 // check name
395 if (SEqual(szStr1: pAlgo->Identifier, szStr2: szName))
396 // success!
397 return pAlgo;
398 // nothing found
399 return nullptr;
400}
401
402void C4MCOverlay::Evaluate()
403{
404 // inherited
405 C4MCNode::Evaluate();
406 // get mat color
407 if (Inside<int32_t>(ival: Material, lbound: 0, rbound: MapCreator->MatMap->Num - 1))
408 {
409 MatClr = MapCreator->TexMap->GetIndexMatTex(szMaterialTexture: MapCreator->MatMap->Map[Material].Name, szDefaultTexture: *Texture ? Texture : nullptr);
410 if (Sub) MatClr += 128;
411 }
412 else
413 MatClr = 0;
414 // calc size
415 if (Owner)
416 {
417 C4MCOverlay *pOwnrOvrl;
418 if (pOwnrOvrl = OwnerOverlay())
419 {
420 int32_t iOwnerWdt = pOwnrOvrl->Wdt; int32_t iOwnerHgt = pOwnrOvrl->Hgt;
421 X = RX.Evaluate(relative_to: iOwnerWdt) + pOwnrOvrl->X;
422 Y = RY.Evaluate(relative_to: iOwnerHgt) + pOwnrOvrl->Y;
423 Wdt = RWdt.Evaluate(relative_to: iOwnerWdt);
424 Hgt = RHgt.Evaluate(relative_to: iOwnerHgt);
425 OffX = ROffX.Evaluate(relative_to: iOwnerWdt);
426 OffY = ROffY.Evaluate(relative_to: iOwnerHgt);
427 }
428 }
429 // calc seed
430 if (!(Seed = FixedSeed)) Seed = (Random(iRange: 32768) << 16) | Random(iRange: 65536);
431}
432
433C4MCOverlay *C4MCOverlay::FirstOfChain()
434{
435 // run backwards until nullptr, non-overlay or overlay without operator is found
436 C4MCOverlay *pOvrl = this;
437 C4MCOverlay *pPrevO;
438 while (pOvrl->Prev)
439 {
440 if (!(pPrevO = pOvrl->Prev->Overlay())) break;
441 if (pPrevO->Op == MCT_NONE) break;
442 pOvrl = pPrevO;
443 }
444 // done
445 return pOvrl;
446}
447
448bool C4MCOverlay::CheckMask(int32_t iX, int32_t iY)
449{
450 // bounds match?
451 if (!LooseBounds) if (iX < X || iY < Y || iX >= X + Wdt || iY >= Y + Hgt) return false;
452#ifdef DEBUGREC
453 C4RCTrf rc;
454 rc.x = iX; rc.y = iY; rc.Rotate = Rotate; rc.Turbulence = Turbulence;
455 AddDbgRec(RCT_MCT1, &rc, sizeof(rc));
456#endif
457 C4Fixed dX = itofix(x: iX); C4Fixed dY = itofix(x: iY);
458 // apply turbulence
459 if (Turbulence)
460 {
461 const C4Fixed Rad2Grad = itofix(x: 3754936, prec: 65536);
462 int32_t j = 3;
463 for (int32_t i = 10; i <= Turbulence; i *= 10)
464 {
465 int32_t Seed2; Seed2 = Seed;
466 for (int32_t l = 0; l <= Lambda; ++l)
467 {
468 for (C4Fixed d = itofix(x: 2); d < 6; d += FIXED10(x: 15))
469 {
470 dX += Sin(fAngle: ((dX / 7 + itofix(x: Seed2) / ZoomX + dY) / j + d) * Rad2Grad) * j / 2;
471 dY += Cos(fAngle: ((dY / 7 + itofix(x: Seed2) / ZoomY + dX) / j - d) * Rad2Grad) * j / 2;
472 }
473 Seed2 = (Seed * (Seed2 << 3) + 0x4465) & 0xffff;
474 }
475 j += 3;
476 }
477 }
478 // apply rotation
479 if (Rotate)
480 {
481 C4Fixed dXo(dX), dYo(dY);
482 dX = dXo * Cos(fAngle: itofix(x: Rotate)) - dYo * Sin(fAngle: itofix(x: Rotate));
483 dY = dYo * Cos(fAngle: itofix(x: Rotate)) + dXo * Sin(fAngle: itofix(x: Rotate));
484 }
485 if (Rotate || Turbulence)
486 {
487 iX = fixtoi(x: dX, prec: ZoomX); iY = fixtoi(x: dY, prec: ZoomY);
488 }
489 else
490 {
491 iX *= ZoomX; iY *= ZoomY;
492 }
493#ifdef DEBUGREC
494 C4RCPos rc2;
495 rc2.x = iX; rc2.y = iY;
496 AddDbgRec(RCT_MCT2, &rc2, sizeof(rc2));
497#endif
498 // apply offset
499 iX -= OffX * ZoomX; iY -= OffY * ZoomY;
500 // check bounds, if loose
501 if (LooseBounds) if (iX < X * ZoomX || iY < Y * ZoomY || iX >= (X + Wdt) * ZoomX || iY >= (Y + Hgt) * ZoomY) return Invert;
502 // query algorithm
503 return (Algorithm->Function)(this, iX, iY) ^ Invert;
504}
505
506bool C4MCOverlay::RenderPix(int32_t iX, int32_t iY, uint8_t &rPix, C4MCTokenType eLastOp, bool fLastSet, bool fDraw, C4MCOverlay **ppPixelSetOverlay)
507{
508 // algo match?
509 bool SetThis = CheckMask(iX, iY);
510 bool DoSet;
511 // exec last op
512 switch (eLastOp)
513 {
514 case MCT_AND: // and
515 DoSet = SetThis && fLastSet;
516 break;
517 case MCT_OR: // or
518 DoSet = SetThis || fLastSet;
519 break;
520 case MCT_XOR: // xor
521 DoSet = SetThis ^ fLastSet;
522 break;
523 default: // no op
524 DoSet = SetThis;
525 break;
526 }
527
528 // set pix to local value and exec children, if no operator is following
529 if ((DoSet && fDraw && Op == MCT_NONE) || Group)
530 {
531 // groups don't set a pixel value, if they're associated with an operator
532 fDraw &= !Group || (Op == MCT_NONE);
533 if (fDraw && DoSet && !Mask)
534 {
535 rPix = MatClr;
536 if (ppPixelSetOverlay) *ppPixelSetOverlay = this;
537 }
538 bool fLastSetC = false; eLastOp = MCT_NONE;
539 // evaluate children overlays, if this was painted, too
540 for (C4MCNode *pChild = Child0; pChild; pChild = pChild->Next)
541 if (C4MCOverlay *pOvrl = pChild->Overlay())
542 {
543 fLastSetC = pOvrl->RenderPix(iX, iY, rPix, eLastOp, fLastSet: fLastSetC, fDraw, ppPixelSetOverlay);
544 if (Group && (pOvrl->Op == MCT_NONE))
545 DoSet |= fLastSetC;
546 eLastOp = pOvrl->Op;
547 }
548 // add evaluation-callback
549 if (pEvaluateFunc && DoSet && fDraw) pEvaluateFunc->EnablePixel(iX, iY);
550 }
551 // done
552 return DoSet;
553}
554
555bool C4MCOverlay::PeekPix(int32_t iX, int32_t iY)
556{
557 // start with this one
558 C4MCOverlay *pOvrl = this; bool fLastSetC = false; C4MCTokenType eLastOp = MCT_NONE; uint8_t Crap;
559 // loop through op chain
560 while (1)
561 {
562 fLastSetC = pOvrl->RenderPix(iX, iY, rPix&: Crap, eLastOp, fLastSet: fLastSetC, fDraw: false);
563 eLastOp = pOvrl->Op;
564 if (!pOvrl->Op) break;
565 // must be another overlay, since there's an operator
566 // hopefully, the preparser will catch all the other crap
567 pOvrl = pOvrl->Next->Overlay();
568 }
569 // return result
570 return fLastSetC;
571}
572
573// point
574
575C4MCPoint::C4MCPoint(C4MCNode *pOwner) : C4MCNode(pOwner)
576{
577 // zero members
578 X = Y = 0;
579}
580
581C4MCPoint::C4MCPoint(C4MCNode *pOwner, C4MCPoint &rTemplate, bool fClone) : C4MCNode(pOwner, rTemplate, fClone)
582{
583 // copy fields
584 X = rTemplate.X; Y = rTemplate.Y;
585 RX = rTemplate.RX; RY = rTemplate.RY;
586}
587
588void C4MCPoint::Default()
589{
590 X = Y = 0;
591}
592
593bool C4MCPoint::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
594{
595 // only explicit %/px
596 if (ValType == MCT_INT) return false;
597 if (SEqual(szStr1: szField, szStr2: "x"))
598 {
599 RX.Set(IntPar, percent: ValType == MCT_PERCENT);
600 return true;
601 }
602 else if (SEqual(szStr1: szField, szStr2: "y"))
603 {
604 RY.Set(IntPar, percent: ValType == MCT_PERCENT);
605 return true;
606 }
607 return false;
608}
609
610void C4MCPoint::Evaluate()
611{
612 // inherited
613 C4MCNode::Evaluate();
614 // get mat color
615 // calc size
616 if (Owner)
617 {
618 C4MCOverlay *pOwnrOvrl;
619 if (pOwnrOvrl = OwnerOverlay())
620 {
621 X = RX.Evaluate(relative_to: pOwnrOvrl->Wdt) + pOwnrOvrl->X;
622 Y = RY.Evaluate(relative_to: pOwnrOvrl->Hgt) + pOwnrOvrl->Y;
623 }
624 }
625}
626
627// map
628
629C4MCMap::C4MCMap(C4MCNode *pOwner) : C4MCOverlay(pOwner) {}
630
631C4MCMap::C4MCMap(C4MCNode *pOwner, C4MCMap &rTemplate, bool fClone) : C4MCOverlay(pOwner, rTemplate, fClone) {}
632
633void C4MCMap::Default()
634{
635 // inherited
636 C4MCOverlay::Default();
637 // size by landscape def
638 Wdt = MapCreator->Landscape->MapWdt.Evaluate();
639 Hgt = MapCreator->Landscape->MapHgt.Evaluate();
640 // map player extend
641 MapCreator->PlayerCount = (std::max)(a: MapCreator->PlayerCount, b: 1);
642 if (MapCreator->Landscape->MapPlayerExtend)
643 Wdt = (std::min)(a: Wdt * (std::min)(a: MapCreator->PlayerCount, b: C4S_MaxMapPlayerExtend), b: MapCreator->Landscape->MapWdt.Max);
644}
645
646bool C4MCMap::RenderTo(uint8_t *pToBuf, int32_t iPitch)
647{
648 // set current render target
649 if (MapCreator) MapCreator->pCurrentMap = this;
650 // draw pixel by pixel
651 for (int32_t iY = 0; iY < Hgt; iY++)
652 {
653 for (int32_t iX = 0; iX < Wdt; iX++)
654 {
655 // default to sky
656 *pToBuf = 0;
657 // render pixel value
658 C4MCOverlay *pRenderedOverlay = nullptr;
659 RenderPix(iX, iY, rPix&: *pToBuf, eLastOp: MCT_NONE, fLastSet: false, fDraw: true, ppPixelSetOverlay: &pRenderedOverlay);
660 // add draw-callback for rendered overlay
661 if (pRenderedOverlay)
662 if (pRenderedOverlay->pDrawFunc)
663 pRenderedOverlay->pDrawFunc->EnablePixel(iX, iY);
664 // next pixel
665 pToBuf++;
666 }
667 // next line
668 pToBuf += iPitch - Wdt;
669 }
670 // reset render target
671 if (MapCreator) MapCreator->pCurrentMap = nullptr;
672 // success
673 return true;
674}
675
676void C4MCMap::SetSize(int32_t iWdt, int32_t iHgt)
677{
678 // store new size
679 Wdt = iWdt; Hgt = iHgt;
680 // update relative values
681 MapCreator->ReEvaluate();
682}
683
684// map creator
685
686C4MapCreatorS2::C4MapCreatorS2(C4SLandscape *pLandscape, C4TextureMap *pTexMap, C4MaterialMap *pMatMap, int iPlayerCount) : C4MCNode(nullptr)
687{
688 // me r b creator
689 MapCreator = this;
690 // store members
691 Landscape = pLandscape; TexMap = pTexMap; MatMap = pMatMap;
692 PlayerCount = iPlayerCount;
693 // set engine field for default stuff
694 DefaultMap.MapCreator = this;
695 DefaultOverlay.MapCreator = this;
696 DefaultPoint.MapCreator = this;
697 // default to landscape settings
698 Default();
699}
700
701C4MapCreatorS2::C4MapCreatorS2(C4MapCreatorS2 &rTemplate, C4SLandscape *pLandscape) : C4MCNode(nullptr, rTemplate, true)
702{
703 // me r b creator
704 MapCreator = this;
705 // store members
706 Landscape = pLandscape; TexMap = rTemplate.TexMap; MatMap = rTemplate.MatMap;
707 PlayerCount = rTemplate.PlayerCount;
708 // set engine field for default stuff
709 DefaultMap.MapCreator = this;
710 DefaultOverlay.MapCreator = this;
711 DefaultPoint.MapCreator = this;
712 // default to landscape settings
713 Default();
714}
715
716C4MapCreatorS2::~C4MapCreatorS2()
717{
718 // clear fields
719 Clear();
720}
721
722void C4MapCreatorS2::Default()
723{
724 // default templates
725 DefaultMap.Default();
726 DefaultOverlay.Default();
727 DefaultPoint.Default();
728 pCurrentMap = nullptr;
729}
730
731void C4MapCreatorS2::Clear()
732{
733 // clear nodes
734 C4MCNode::Clear();
735 // clear callbacks
736 CallbackArrays.Clear();
737 // defaults templates
738 Default();
739}
740
741bool C4MapCreatorS2::ReadFile(const char *szFilename, C4Group *pGrp)
742{
743 // create parser and read file
744 try
745 {
746 C4MCParser(this).ParseFile(szFilename, pGrp);
747 }
748 catch (const C4MCParserErr &err)
749 {
750 err.show();
751 return false;
752 }
753 // success
754 return true;
755}
756
757bool C4MapCreatorS2::ReadScript(const char *szScript)
758{
759 // create parser and read
760 try
761 {
762 C4MCParser(this).Parse(szScript);
763 }
764 catch (const C4MCParserErr &err)
765 {
766 err.show();
767 return false;
768 }
769 // success
770 return true;
771}
772
773C4MCMap *C4MapCreatorS2::GetMap(const char *szMapName)
774{
775 C4MCMap *pMap = nullptr; C4MCNode *pNode;
776 // get map
777 if (szMapName && *szMapName)
778 {
779 // by name...
780 if (pNode = GetNodeByName(szName: szMapName))
781 if (pNode->Type() == MCN_Map)
782 pMap = static_cast<C4MCMap *>(pNode);
783 }
784 else
785 {
786 // or simply last map entry
787 for (pNode = ChildL; pNode; pNode = pNode->Prev)
788 if (pNode->Type() == MCN_Map)
789 {
790 pMap = static_cast<C4MCMap *>(pNode);
791 break;
792 }
793 }
794 return pMap;
795}
796
797CSurface8 *C4MapCreatorS2::Render(const char *szMapName)
798{
799 // get map
800 C4MCMap *pMap = GetMap(szMapName);
801 if (!pMap) return nullptr;
802
803 // get size
804 int32_t sfcWdt, sfcHgt;
805 sfcWdt = pMap->Wdt; sfcHgt = pMap->Hgt;
806 if (!sfcWdt || !sfcHgt) return nullptr;
807
808 // create surface
809 CSurface8 *sfc = new CSurface8(sfcWdt, sfcHgt);
810
811 // render map to surface
812 pMap->RenderTo(pToBuf: sfc->Bits, iPitch: sfc->Pitch);
813
814 // success
815 return sfc;
816}
817
818C4MCParserErr::C4MCParserErr(C4MCParser *pParser, const std::string_view msg)
819 : Msg{std::format(fmt: "{}: {} ({})", args: +pParser->Filename, args: msg, args: pParser->Code ? SGetLine(szText: pParser->Code, cpPosition: pParser->CPos) : 0)}
820{
821}
822
823void C4MCParserErr::show() const
824{
825 // log error
826 LogNTr(level: spdlog::level::err, message: Msg);
827}
828
829// parser
830
831C4MCParser::C4MCParser(C4MapCreatorS2 *pMapCreator)
832{
833 // store map creator
834 MapCreator = pMapCreator;
835 // reset some fields
836 Code = nullptr; CPos = nullptr; *Filename = 0;
837}
838
839C4MCParser::~C4MCParser()
840{
841 // clean up
842 Clear();
843}
844
845void C4MCParser::Clear()
846{
847 // clear code if present
848 delete[] Code; Code = nullptr; CPos = nullptr;
849 // reset filename
850 *Filename = 0;
851}
852
853bool C4MCParser::AdvanceSpaces()
854{
855 char C, C2{0};
856 // defaultly, not in comment
857 int32_t InComment = 0; // 0/1/2 = no comment/line comment/multi line comment
858 // don't go past end
859 while (C = *CPos)
860 {
861 // loop until out of comment and non-whitespace is found
862 switch (InComment)
863 {
864 case 0:
865 if (C == '/')
866 {
867 CPos++;
868 switch (*CPos)
869 {
870 case '/': InComment = 1; break;
871 case '*': InComment = 2; break;
872 default: CPos--; return true;
873 }
874 }
875 else if (static_cast<uint8_t>(C) > 32) return true;
876 break;
877 case 1:
878 if ((static_cast<uint8_t>(C) == 13) || (static_cast<uint8_t>(C) == 10)) InComment = 0;
879 break;
880 case 2:
881 if ((C == '/') && (C2 == '*')) InComment = 0;
882 break;
883 }
884 // next char; store prev
885 CPos++; C2 = C;
886 }
887 // end of code reached; return false
888 return false;
889}
890
891bool C4MCParser::GetNextToken()
892{
893 // move to start of token
894 if (!AdvanceSpaces()) { CurrToken = MCT_EOF; return false; }
895 // store offset
896 const char *CPos0 = CPos;
897 int32_t Len = 0;
898 // token get state
899 enum TokenGetState
900 {
901 TGS_None, // just started
902 TGS_Ident, // getting identifier
903 TGS_Int, // getting integer
904 TGS_Dir // getting directive
905 };
906 TokenGetState State = TGS_None;
907
908 // loop until finished
909 while (true)
910 {
911 // get char
912 char C = *CPos;
913
914 switch (State)
915 {
916 case TGS_None:
917 // get token type by first char
918 // +/- are operators
919 if (((C >= '0') && (C <= '9') || (C == '+') || (C == '-'))) // integer by +, -, 0-9
920 State = TGS_Int;
921 else if (C == '#') State = TGS_Dir; // directive by "#"
922 else if (C == ';') { CPos++; CurrToken = MCT_SCOLON; return true; } // ";"
923 else if (C == '=') { CPos++; CurrToken = MCT_EQ; return true; } // "="
924 else if (C == '{') { CPos++; CurrToken = MCT_BLOPEN; return true; } // "{"
925 else if (C == '}') { CPos++; CurrToken = MCT_BLCLOSE; return true; } // "}"
926 else if (C == '&') { CPos++; CurrToken = MCT_AND; return true; } // "&"
927 else if (C == '|') { CPos++; CurrToken = MCT_OR; return true; } // "|"
928 else if (C == '^') { CPos++; CurrToken = MCT_XOR; return true; } // "^"
929 else if (C >= '@') State = TGS_Ident; // identifier by all non-special chars
930 else
931 {
932 // unrecognized char
933 CPos++;
934 throw C4MCParserErr(this, "unexpected character found");
935 }
936 break;
937
938 case TGS_Ident: // ident and directive: parse until non ident-char is found
939 case TGS_Dir:
940 if (((C < '0') || (C > '9')) && ((C < 'a') || (C > 'z')) && ((C < 'A') || (C > 'Z')) && (C != '_'))
941 {
942 // return ident/directive
943 Len = std::min<int32_t>(a: Len, b: C4MaxName);
944 SCopy(szSource: CPos0, sTarget: CurrTokenIdtf, iMaxL: Len);
945 if (State == TGS_Ident) CurrToken = MCT_IDTF; else CurrToken = MCT_DIR;
946 return true;
947 }
948 break;
949
950 case TGS_Int: // integer: parse until non-number is found
951 if ((C < '0') || (C > '9'))
952 {
953 // return integer
954 Len = std::min<int32_t>(a: Len, b: C4MaxName);
955 CurrToken = MCT_INT;
956 // check for "-"
957 if (Len == 1 && *CPos0 == '-')
958 {
959 CurrToken = MCT_RANGE;
960 return true;
961 }
962 else if ('%' == C) { CPos++; CurrToken = MCT_PERCENT; } // "%"
963 else if ('p' == C)
964 {
965 // p or px
966 ++CPos;
967 if ('x' == *CPos) ++CPos;
968 CurrToken = MCT_PX;
969 }
970 SCopy(szSource: CPos0, sTarget: CurrTokenIdtf, iMaxL: Len);
971 // it's not, so return the int32_t
972 sscanf(s: CurrTokenIdtf, format: "%d", &CurrTokenVal);
973 return true;
974 }
975 break;
976 }
977 // next char
978 CPos++; Len++;
979 }
980}
981
982static void PrintNodeTree(C4MCNode *pNode, int depth)
983{
984 for (int i = 0; i < depth; ++i)
985 printf(format: " ");
986 switch (pNode->Type())
987 {
988 case MCN_Node: printf(format: "Node %s\n", pNode->Name); break;
989 case MCN_Overlay: printf(format: "Overlay %s\n", pNode->Name); break;
990 case MCN_Point: printf(format: "Point %s\n", pNode->Name); break;
991 case MCN_Map: printf(format: "Map %s\n", pNode->Name); break;
992 }
993 for (C4MCNode *pChild = pNode->Child0; pChild; pChild = pChild->Next)
994 PrintNodeTree(pNode: pChild, depth: depth + 1);
995}
996
997void C4MCParser::ParseTo(C4MCNode *pToNode)
998{
999 C4MCNode *pNewNode = nullptr; // new node
1000 bool Done = false; // finished?
1001 C4MCNodeType LastOperand; // last first operand of operator
1002 char FieldName[C4MaxName]; // buffer for current field to access
1003 C4MCNode *pCpyNode; // node to copy from
1004 // current state
1005 enum ParseState
1006 {
1007 PS_NONE, // just started
1008 PS_KEYWD1, // got block-opening keyword (map, overlay etc.)
1009 PS_KEYWD1N, // got name for block
1010 PS_AFTERNODE, // node has been parsed; expect ; or operator
1011 PS_GOTOP, // got operator
1012 PS_GOTIDTF, // got identifier, expect '=', ';' or '{'; identifier remains in CurrTokenIdtf
1013 PS_GOTOPIDTF, // got identifier after operator; accept ';' or '{' only
1014 PS_SETFIELD // accept field value; field is stored in FieldName
1015 };
1016 ParseState State = PS_NONE;
1017 // parse until end of file (or block)
1018 while (GetNextToken())
1019 {
1020 switch (State)
1021 {
1022 case PS_NONE:
1023 case PS_GOTOP:
1024 switch (CurrToken)
1025 {
1026 case MCT_DIR:
1027 // top level needed
1028 if (!pToNode->GlobalScope())
1029 throw C4MCParserErr(this, C4MCErr_NoDirGlobal);
1030 // no directives so far
1031 throw C4MCParserErr(this, C4MCErr_UnknownDir, +CurrTokenIdtf);
1032 break;
1033 case MCT_IDTF:
1034 // identifier: check keywords
1035 if (SEqual(szStr1: CurrTokenIdtf, C4MC_Overlay))
1036 {
1037 // overlay: create overlay node, using default template
1038 pNewNode = new C4MCOverlay(pToNode, MapCreator->DefaultOverlay, false);
1039 State = PS_KEYWD1;
1040 }
1041 else if (SEqual(szStr1: CurrTokenIdtf, C4MC_Point) && !pToNode->GetNodeByName(szName: CurrTokenIdtf))
1042 {
1043 // only in overlays
1044 if (!pToNode->Type() == MCN_Overlay)
1045 throw C4MCParserErr(this, C4MCErr_PointOnlyOvl);
1046 // create point node, using default template
1047 pNewNode = new C4MCPoint(pToNode, MapCreator->DefaultPoint, false);
1048 State = PS_KEYWD1;
1049 }
1050 else if (SEqual(szStr1: CurrTokenIdtf, C4MC_Map))
1051 {
1052 // map: check top level
1053 if (!pToNode->GlobalScope())
1054 throw C4MCParserErr(this, C4MCErr_MapNoGlobal);
1055 // create map node, using default template
1056 pNewNode = new C4MCMap(pToNode, MapCreator->DefaultMap, false);
1057 State = PS_KEYWD1;
1058 }
1059 else
1060 {
1061 // so this is either a field-set or a defined node
1062 // '=', ';' or '{' may follow, none of these will clear the CurrTokenIdtf
1063 // so safely assume it preserved and just update the state
1064 if (State == PS_GOTOP) State = PS_GOTOPIDTF; else State = PS_GOTIDTF;
1065 }
1066 // operator: check type
1067 if (State == PS_GOTOP && pNewNode)
1068 if (LastOperand != pNewNode->Type())
1069 throw C4MCParserErr(this, C4MCErr_OpTypeErr);
1070 break;
1071 case MCT_BLCLOSE:
1072 case MCT_EOF:
1073 // block done
1074 Done = true;
1075 break;
1076 default:
1077 // we don't like that
1078 throw C4MCParserErr(this, C4MCErr_IdtfExp);
1079 break;
1080 }
1081 break;
1082 case PS_KEYWD1:
1083 if (CurrToken == MCT_IDTF)
1084 {
1085 // name the current node
1086 SCopy(szSource: CurrTokenIdtf, sTarget: pNewNode->Name, iMaxL: C4MaxName);
1087 State = PS_KEYWD1N;
1088 break;
1089 }
1090 else if (pToNode->GlobalScope())
1091 {
1092 // disallow unnamed nodes in global scope
1093 throw C4MCParserErr(this, C4MCErr_UnnamedNoGlbl);
1094 }
1095 // in local scope, allow unnamed; so continue
1096 case PS_KEYWD1N:
1097 // do expect a block opening
1098 if (CurrToken != MCT_BLOPEN)
1099 throw C4MCParserErr(this, C4MCErr_BlOpenExp);
1100 // parse new node
1101 ParseTo(pToNode: pNewNode);
1102 // check file end
1103 if (CurrToken == MCT_EOF)
1104 throw C4MCParserErr(this, C4MCErr_EOF);
1105 // reset state
1106 State = PS_AFTERNODE;
1107 break;
1108 case PS_GOTIDTF:
1109 case PS_GOTOPIDTF:
1110 switch (CurrToken)
1111 {
1112 case MCT_EQ:
1113 // so it's a field set
1114 // not after operators
1115 if (State == PS_GOTOPIDTF)
1116 throw C4MCParserErr(this, C4MCErr_Obj2Exp);
1117 // store field name
1118 SCopy(szSource: CurrTokenIdtf, sTarget: FieldName, iMaxL: C4MaxName);
1119 // update state to accept value
1120 State = PS_SETFIELD;
1121 break;
1122 case MCT_BLOPEN:
1123 case MCT_SCOLON:
1124 case MCT_AND: case MCT_OR: case MCT_XOR:
1125 // so it's a node copy
1126 // local scope only
1127 if (pToNode->GlobalScope())
1128 throw C4MCParserErr(this, C4MCErr_ReinstNoGlobal, +CurrTokenIdtf);
1129 // get the node
1130 pCpyNode = pToNode->GetNodeByName(szName: CurrTokenIdtf);
1131 if (!pCpyNode)
1132 throw C4MCParserErr(this, C4MCErr_UnknownObj, +CurrTokenIdtf);
1133 // create the copy
1134 switch (pCpyNode->Type())
1135 {
1136 case MCN_Overlay:
1137 // create overlay
1138 pNewNode = new C4MCOverlay(pToNode, *static_cast<C4MCOverlay *>(pCpyNode), false);
1139 break;
1140 case MCN_Map:
1141 // maps not allowed
1142 if (pCpyNode->Type() == MCN_Map)
1143 throw C4MCParserErr(this, C4MCErr_MapNoGlobal, +CurrTokenIdtf);
1144 break;
1145 default:
1146 // huh?
1147 throw C4MCParserErr(this, C4MCErr_ReinstUnknown, +CurrTokenIdtf);
1148 break;
1149 }
1150 // check type for operators
1151 if (State == PS_GOTOPIDTF)
1152 if (LastOperand != pNewNode->Type())
1153 throw C4MCParserErr(this, C4MCErr_OpTypeErr);
1154 // further overloads?
1155 if (CurrToken == MCT_BLOPEN)
1156 {
1157 // parse new node
1158 ParseTo(pToNode: pNewNode);
1159 // get next token, as we'll simply fall through to PS_AFTERNODE
1160 GetNextToken();
1161 // check file end
1162 if (CurrToken == MCT_EOF)
1163 throw C4MCParserErr(this, C4MCErr_EOF);
1164 }
1165 // reset state
1166 State = PS_AFTERNODE;
1167 break;
1168
1169 default:
1170 throw C4MCParserErr(this, C4MCErr_EqSColonBlOpenExp);
1171 break;
1172 }
1173 // fall through to next case, if it was a named node reinstanciation
1174 if (State != PS_AFTERNODE) break;
1175 case PS_AFTERNODE:
1176 // expect operator or semicolon
1177 switch (CurrToken)
1178 {
1179 case MCT_SCOLON:
1180 // reset state
1181 State = PS_NONE;
1182 break;
1183 case MCT_AND:
1184 case MCT_OR:
1185 case MCT_XOR:
1186 // operator: not in global scope
1187 if (pToNode->GlobalScope())
1188 throw C4MCParserErr(this, C4MCErr_OpsNoGlobal);
1189 // set operator
1190 if (!pNewNode->SetOp(CurrToken))
1191 throw C4MCParserErr(this, "';' expected");
1192 LastOperand = pNewNode->Type();
1193 // update state
1194 State = PS_GOTOP;
1195 break;
1196 default:
1197 throw C4MCParserErr(this, C4MCErr_SColonOrOpExp);
1198 break;
1199 }
1200 // node done
1201 // evaluate node and children, if this is top-level
1202 // we mustn't evaluate everything immediately, because parents must be evaluated first!
1203 if (pToNode->GlobalScope()) pNewNode->ReEvaluate();
1204 pNewNode = nullptr;
1205 break;
1206 case PS_SETFIELD:
1207 ParseValue(pToNode, szFieldName: FieldName);
1208 // reset state
1209 State = PS_NONE;
1210 break;
1211 }
1212 // don't get another token!
1213 if (Done) break;
1214 }
1215 // end of file expected?
1216 if (State != PS_NONE)
1217 {
1218 if (State == PS_GOTOP)
1219 throw C4MCParserErr(this, C4MCErr_Obj2Exp);
1220 else
1221 throw C4MCParserErr(this, C4MCErr_EOF);
1222 }
1223}
1224
1225void C4MCParser::ParseValue(C4MCNode *pToNode, const char *szFieldName)
1226{
1227 int32_t Value;
1228 C4MCTokenType Type;
1229 switch (CurrToken)
1230 {
1231 case MCT_IDTF:
1232 {
1233 // set field
1234 if (!pToNode->SetField(pParser: this, szField: szFieldName, szSVal: CurrTokenIdtf, iVal: 0, ValType: CurrToken))
1235 // field not found
1236 throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
1237 if (!GetNextToken())
1238 throw C4MCParserErr(this, C4MCErr_EOF);
1239 break;
1240 }
1241 case MCT_INT:
1242 case MCT_PX:
1243 case MCT_PERCENT:
1244 {
1245 Value = CurrTokenVal;
1246 Type = CurrToken;
1247 if (!GetNextToken())
1248 throw C4MCParserErr(this, C4MCErr_EOF);
1249 // range
1250 if (MCT_RANGE == CurrToken)
1251 {
1252 // Get the second value
1253 if (!GetNextToken())
1254 throw C4MCParserErr(this, C4MCErr_EOF);
1255 if (MCT_INT == CurrToken || MCT_PX == CurrToken || MCT_PERCENT == CurrToken)
1256 {
1257 Value += Random(iRange: CurrTokenVal - Value);
1258 }
1259 else
1260 throw C4MCParserErr(this, C4MCErr_FieldConstExp, +CurrTokenIdtf);
1261 Type = CurrToken;
1262 if (!GetNextToken())
1263 throw C4MCParserErr(this, C4MCErr_EOF);
1264 }
1265 if (!pToNode->SetField(pParser: this, szField: szFieldName, szSVal: CurrTokenIdtf, iVal: Value, ValType: Type))
1266 // field not found
1267 throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
1268 break;
1269 }
1270 default:
1271 {
1272 throw C4MCParserErr(this, C4MCErr_FieldConstExp, +CurrTokenIdtf);
1273 }
1274 }
1275
1276 // now, the one and only thing to get is a semicolon
1277 if (CurrToken != MCT_SCOLON)
1278 throw C4MCParserErr(this, C4MCErr_SColonExp);
1279}
1280
1281void C4MCParser::ParseFile(const char *szFilename, C4Group *pGrp)
1282{
1283 size_t iSize; // file size
1284
1285 // clear any old data
1286 Clear();
1287 // store filename
1288 SCopy(szSource: szFilename, sTarget: Filename, iMaxL: C4MaxName);
1289 // check group
1290 if (!pGrp) throw C4MCParserErr(this, C4MCErr_NoGroup);
1291 // get file
1292 if (!pGrp->AccessEntry(szWildCard: szFilename, iSize: &iSize))
1293 // 404
1294 throw C4MCParserErr(this, C4MCErr_404);
1295 // file is empty?
1296 if (!iSize) return;
1297 // alloc mem
1298 Code = new char[iSize + 1];
1299 // read file
1300 pGrp->Read(pBuffer: static_cast<void *>(Code), iSize);
1301 Code[iSize] = 0;
1302 // parse it
1303 CPos = Code;
1304 ParseTo(pToNode: MapCreator);
1305 if (0) PrintNodeTree(pNode: MapCreator, depth: 0);
1306 // free code
1307 // on errors, this will be done be destructor
1308 Clear();
1309}
1310
1311void C4MCParser::Parse(const char *szScript)
1312{
1313 // clear any old data
1314 Clear();
1315 // parse it
1316 CPos = szScript;
1317 ParseTo(pToNode: MapCreator);
1318 if (0) PrintNodeTree(pNode: MapCreator, depth: 0);
1319 // free code
1320 // on errors, this will be done be destructor
1321 Clear();
1322}
1323
1324// algorithms
1325
1326// helper func
1327bool PreparePeek(C4MCOverlay **ppOvrl, int32_t &iX, int32_t &iY, C4MCOverlay **ppTopOvrl)
1328{
1329 // zoom out
1330 iX /= (*ppOvrl)->ZoomX; iY /= (*ppOvrl)->ZoomY;
1331 // get owning overlay
1332 C4MCOverlay *pOvrl2 = (*ppOvrl)->OwnerOverlay();
1333 if (!pOvrl2) return false;
1334 // get uppermost overlay
1335 C4MCOverlay *pNextOvrl;
1336 for (*ppTopOvrl = pOvrl2; pNextOvrl = (*ppTopOvrl)->OwnerOverlay(); *ppTopOvrl = pNextOvrl);
1337 // get first of operator-chain
1338 pOvrl2 = pOvrl2->FirstOfChain();
1339 // set new overlay
1340 *ppOvrl = pOvrl2;
1341 // success
1342 return true;
1343}
1344
1345#define a pOvrl->Alpha
1346#define b pOvrl->Beta
1347#define s pOvrl->Seed
1348#define z C4MC_ZoomRes
1349#define z2 (C4MC_ZoomRes * C4MC_ZoomRes)
1350
1351bool AlgoSolid(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1352{
1353 // solid always solid :)
1354 return true;
1355}
1356
1357bool AlgoRandom(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1358{
1359 // totally random
1360 return !((((s ^ (iX << 2) ^ (iY << 5)) ^ ((s >> 16) + 1 + iX + (iY << 2))) / 17) % (a.Evaluate(C4MC_SizeRes) + 2));
1361}
1362
1363bool AlgoChecker(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1364{
1365 // checkers with size of 10
1366 return !(((iX / (z * 10)) % 2) ^ ((iY / (z * 10)) % 2));
1367}
1368
1369bool AlgoBozo(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1370{
1371 // do some bozo stuff - keep it regular here, since it may be modified by turbulence
1372 int32_t iXC = (iX / 10 + s + (iY / 80)) % (z * 2) - z;
1373 int32_t iYC = (iY / 10 + s + (iX / 80)) % (z * 2) - z;
1374 int32_t id = Abs(val: iXC * iYC); // ((iSeed^iX^iY)%z)
1375 return id > z2 * (a.Evaluate(C4MC_SizeRes) + 10) / 50;
1376}
1377
1378bool AlgoSin(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1379{
1380 // a sine curve where bottom is filled
1381 return iY > fixtoi(x: (Sin(fAngle: itofix(x: iX / z * 10)) + 1) * z * 10);
1382}
1383
1384bool AlgoBoxes(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1385{
1386 // For percents instead of Pixels
1387 int32_t pxb = b.Evaluate(relative_to: pOvrl->Wdt);
1388 int32_t pxa = a.Evaluate(relative_to: pOvrl->Wdt);
1389 // return whether inside box
1390 return Abs(val: iX + (s % 4738)) % (pxb * z + 1) < pxa * z + 1 && Abs(val: iY + (s / 4738)) % (pxb * z + 1) < pxa * z + 1;
1391}
1392
1393bool AlgoRndChecker(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1394{
1395 // randomly set squares with size of 10
1396 return AlgoRandom(pOvrl, iX: iX / (z * 10), iY: iY / (z * 10));
1397}
1398
1399bool AlgoLines(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1400{
1401 // For percents instead of Pixels
1402 int32_t pxb = b.Evaluate(relative_to: pOvrl->Wdt);
1403 int32_t pxa = a.Evaluate(relative_to: pOvrl->Wdt);
1404 // return whether inside line
1405 return Abs(val: iX + (s % 4738)) % (pxb * z + 1) < pxa * z + 1;
1406}
1407
1408bool AlgoBorder(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1409{
1410 C4MCOverlay *pTopOvrl;
1411 // get params before, since pOvrl will be changed by PreparePeek
1412 int32_t la = a.Evaluate(relative_to: pOvrl->Wdt); int32_t lb = b.Evaluate(relative_to: pOvrl->Hgt);
1413 // prepare a pixel peek from owner
1414 if (!PreparePeek(ppOvrl: &pOvrl, iX, iY, ppTopOvrl: &pTopOvrl)) return false;
1415 // query a/b pixels in x/y-directions
1416 for (int32_t x = iX - la; x <= iX + la; x++) if (pTopOvrl->InBounds(iX: x, iY)) if (!pOvrl->PeekPix(iX: x, iY)) return true;
1417 for (int32_t y = iY - lb; y <= iY + lb; y++) if (pTopOvrl->InBounds(iX, iY: y)) if (!pOvrl->PeekPix(iX, iY: y)) return true;
1418 // nothing found
1419 return false;
1420}
1421
1422bool AlgoMandel(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1423{
1424 // how many iterations?
1425 uint32_t iMandelIter = a.Evaluate(C4MC_SizeRes) != 0 ? a.Evaluate(C4MC_SizeRes) : 1000;
1426 if (iMandelIter < 10) iMandelIter = 10;
1427 // calc c & ci values
1428 double c = (static_cast<double>(iX) / z / pOvrl->Wdt - .5 * (static_cast<double>(pOvrl->ZoomX) / z)) * 4;
1429 double ci = (static_cast<double>(iY) / z / pOvrl->Hgt - .5 * (static_cast<double>(pOvrl->ZoomY) / z)) * 4;
1430 // create _z & _zi
1431 double _z = c, _zi = ci;
1432 double xz;
1433 uint32_t i;
1434 for (i = 0; i < iMandelIter; i++)
1435 {
1436 xz = _z * _z - _zi * _zi;
1437 _zi = 2 * _z * _zi + ci;
1438 _z = xz + c;
1439 if (_z * _z + _zi * _zi > 4) break;
1440 }
1441 return !(i < iMandelIter);
1442}
1443
1444bool AlgoGradient(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1445{
1446 return (abs(i: (iX ^ (iY * 3)) * 2531011L) % 214013L) % z > iX / pOvrl->Wdt;
1447}
1448
1449bool AlgoScript(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1450{
1451 // get script function
1452 C4AulFunc *pFunc = Game.Script.GetSFunc(pIdtf: (std::string{"ScriptAlgo"} + pOvrl->Name).c_str());
1453 // failsafe
1454 if (!pFunc) return false;
1455 // ok, call func
1456 C4AulParSet Pars(C4VInt(iVal: iX), C4VInt(iVal: iY), C4VInt(iVal: pOvrl->Alpha.Evaluate(C4MC_SizeRes)), C4VInt(iVal: pOvrl->Beta.Evaluate(C4MC_SizeRes)));
1457 // catch error (damn insecure C4Aul)
1458 try
1459 {
1460 return static_cast<bool>(pFunc->Exec(pObj: nullptr, pPars: Pars));
1461 }
1462 catch (const C4AulError &)
1463 {
1464 return false;
1465 }
1466}
1467
1468bool AlgoRndAll(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1469{
1470 // return by seed and params; ignore pos
1471 return s % 100 < a.Evaluate(C4MC_SizeRes);
1472}
1473
1474bool AlgoPolygon(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
1475{
1476 // Wo do not support empty polygons.
1477 if (!pOvrl->ChildL) return false;
1478 int32_t uX = 0; // last point before current point
1479 int32_t uY = 0; // with uY != iY
1480 int32_t cX, cY; // current point
1481 int32_t lX = 0; // x of really last point before current point
1482 int32_t count = 0;
1483 bool ignore = false;
1484 int32_t zX; // Where edge intersects with line
1485 C4MCNode *pChild, *pStartChild;
1486 // get a point with uY!=iY, or anyone
1487 for (pChild = pOvrl->ChildL; pChild->Prev; pChild = pChild->Prev)
1488 if (pChild->Type() == MCN_Point)
1489 {
1490 uX = static_cast<C4MCPoint *>(pChild)->X * 100;
1491 lX = uX;
1492 uY = static_cast<C4MCPoint *>(pChild)->Y * 100;
1493 if (iY != uY) break;
1494 }
1495 pStartChild = pChild->Next;
1496 if (!pStartChild) pStartChild = pOvrl->Child0;
1497 if (!pStartChild) return false;
1498 for (pChild = pStartChild; ; pChild = pChild->Next)
1499 {
1500 if (!pChild) pChild = pOvrl->Child0;
1501 if (pChild->Type() == MCN_Point)
1502 {
1503 cX = static_cast<C4MCPoint *>(pChild)->X * 100;
1504 cY = static_cast<C4MCPoint *>(pChild)->Y * 100;
1505 // If looking at line
1506 if (ignore)
1507 {
1508 // if C is on line
1509 if (cY == iY)
1510 {
1511 // if I is on edge
1512 if (((lX < iX) == (iX < cX)) || (cX == iX)) return true;
1513 }
1514 else
1515 {
1516 // if edge intersects line
1517 if ((uY < iY == iY < cY) && (lX >= iX)) count++;
1518 ignore = false;
1519 uX = cX;
1520 uY = cY;
1521 }
1522 }
1523 // if looking at ray
1524 else
1525 {
1526 // If point C lays on ray
1527 if (cY == iY)
1528 {
1529 // are I and C the same points?
1530 if (cX == iX) return true;
1531 // skip this point for now
1532 ignore = true;
1533 }
1534 else
1535 {
1536 // if edge intersects line
1537 if (uY < iY == iY <= cY)
1538 {
1539 // and edge intersects ray, because both points are right of iX
1540 if (iX < (std::min)(a: uX, b: cX))
1541 {
1542 count++;
1543 }
1544 // or one is right of I
1545 else if (iX <= (std::max)(a: uX, b: cX))
1546 {
1547 // and edge intersects with ray
1548 if (iX < (zX = ((cX - uX) * (iY - uY) / (cY - uY)) + uX)) count++;
1549 // if I lays on CU
1550 if (zX == iX) return true;
1551 }
1552 }
1553 uX = cX;
1554 uY = cY;
1555 }
1556 }
1557 lX = cX;
1558 }
1559 if (pChild->Next == pStartChild) break;
1560 if (!pChild->Next) if (pStartChild == pOvrl->Child0) break;
1561 }
1562 // if edge has changed side of ray uneven times
1563 if ((count & 1) > 0) return true; else return false;
1564}
1565
1566#undef a
1567#undef b
1568#undef s
1569#undef z
1570#undef z2
1571
1572C4MCAlgorithm C4MCAlgoMap[] =
1573{
1574 { .Identifier: "solid", .Function: &AlgoSolid },
1575 { .Identifier: "random", .Function: &AlgoRandom },
1576 { .Identifier: "checker", .Function: &AlgoChecker },
1577 { .Identifier: "bozo", .Function: &AlgoBozo },
1578 { .Identifier: "sin", .Function: &AlgoSin },
1579 { .Identifier: "boxes", .Function: &AlgoBoxes },
1580 { .Identifier: "rndchecker", .Function: &AlgoRndChecker },
1581 { .Identifier: "lines", .Function: &AlgoLines },
1582 { .Identifier: "border", .Function: &AlgoBorder },
1583 { .Identifier: "mandel", .Function: &AlgoMandel },
1584 { .Identifier: "gradient", .Function: &AlgoGradient },
1585 { .Identifier: "script", .Function: &AlgoScript },
1586 { .Identifier: "rndall", .Function: &AlgoRndAll },
1587 { .Identifier: "poly", .Function: &AlgoPolygon },
1588 { .Identifier: "", .Function: nullptr }
1589};
1590
1591C4MCNodeAttr C4MCOvrlMap[] =
1592{
1593 { "x", &C4MCOverlay::RX },
1594 { "y", &C4MCOverlay::RY },
1595 { "wdt", &C4MCOverlay::RWdt },
1596 { "hgt", &C4MCOverlay::RHgt },
1597 { "ox", &C4MCOverlay::ROffX },
1598 { "oy", &C4MCOverlay::ROffY },
1599 { "mat", &C4MCOverlay::Material, C4MCV_Material },
1600 { "tex", &C4MCOverlay::Texture },
1601 { "algo", &C4MCOverlay::Algorithm },
1602 { "sub", &C4MCOverlay::Sub },
1603 { "zoomX", &C4MCOverlay::ZoomX, C4MCV_Zoom },
1604 { "zoomY", &C4MCOverlay::ZoomY, C4MCV_Zoom },
1605 { "a", &C4MCOverlay::Alpha, C4MCV_Pixels },
1606 { "b", &C4MCOverlay::Beta, C4MCV_Pixels },
1607 { "turbulence", &C4MCOverlay::Turbulence },
1608 { "lambda", &C4MCOverlay::Lambda },
1609 { "rotate", &C4MCOverlay::Rotate },
1610 { "seed", &C4MCOverlay::FixedSeed },
1611 { "invert", &C4MCOverlay::Invert },
1612 { "loosebounds", &C4MCOverlay::LooseBounds },
1613 { "grp", &C4MCOverlay::Group },
1614 { "mask", &C4MCOverlay::Mask },
1615 { "evalFn", &C4MCOverlay::pEvaluateFunc },
1616 { "drawFn", &C4MCOverlay::pDrawFunc },
1617 {}
1618};
1619