openclonk/src/landscape/C4MapCreatorS2.cpp

1798 lines
46 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// complex dynamic landscape creator
#include "C4Include.h"
#include "landscape/C4MapCreatorS2.h"
#include "control/C4Record.h"
#include "graphics/CSurface8.h"
#include "landscape/C4Material.h"
#include "landscape/C4Texture.h"
#include "lib/C4Random.h"
#include "script/C4ScriptHost.h"
namespace {
// node attribute entry for SetField search
enum C4MCValueType
{
C4MCV_None,
C4MCV_Integer,
C4MCV_Percent,
C4MCV_Pixels,
C4MCV_Material,
C4MCV_Texture,
C4MCV_Algorithm,
C4MCV_Boolean,
C4MCV_Zoom,
C4MCV_ScriptFunc
};
template<typename T>
class MemberAdapter {
public:
typedef char (T::*OffsetType);
MemberAdapter(T& object, OffsetType offset)
: Object(object), Offset(offset)
{
}
template<typename U>
U& As()
{
typedef U (T::*TargetPtrType);
return Object.*reinterpret_cast<TargetPtrType>(Offset);
}
private:
T& Object;
OffsetType Offset;
};
typedef MemberAdapter<C4MCOverlay>::OffsetType C4MCOverlayOffsetType;
struct C4MCNodeAttr
{
const char* Name; // name of field
C4MCValueType Type; // type of field
C4MCOverlayOffsetType Offset; // offset of field in overlay MCOverlay-class
};
extern C4MCNodeAttr C4MCOvrlMap[];
}
/* --- C4MCCallbackArray --- */
C4MCCallbackArray::C4MCCallbackArray(C4AulFunc *pSFunc, C4MapCreatorS2 *pMapCreator)
{
// store fn
pSF = pSFunc;
// zero fields
pMap=nullptr; pNext=nullptr;
// store and add in map creator
if ((this->pMapCreator=pMapCreator))
pMapCreator->CallbackArrays.Add(this);
// done
}
C4MCCallbackArray::~C4MCCallbackArray()
{
// clear map, if present
if (pMap) delete [] pMap;
}
void C4MCCallbackArray::EnablePixel(int32_t iX, int32_t iY)
{
// array not yet created? then do that now!
if (!pMap)
{
// safety
if (!pMapCreator) return;
// get current map size
C4MCMap *pCurrMap = pMapCreator->pCurrentMap;
if (!pCurrMap) return;
iWdt = pCurrMap->Wdt; iHgt = pCurrMap->Hgt;
// create bitmap
int32_t iSize=(iWdt*iHgt+7)/8;
pMap = new BYTE[iSize];
memset(pMap, 0, iSize);
// done
}
// safety: do not set outside map!
if (iX<0 || iY<0 || iX>=iWdt || iY>=iHgt) return;
// set in map
int32_t iIndex = iX + iY*iWdt;
pMap[iIndex/8] |= 1<<(iIndex%8);
// done
}
void C4MCCallbackArray::Execute(int32_t iMapZoom)
{
// safety
if (!pSF || !pMap) return;
// pre-create parset
C4AulParSet Pars(0, 0, iMapZoom);
// call all funcs
int32_t iIndex=iWdt*iHgt;
while (iIndex--)
if (pMap[iIndex/8]&(1<<(iIndex%8)))
{
// set pars
Pars[0] = C4VInt((iIndex%iWdt) * iMapZoom - (iMapZoom/2));
Pars[1] = C4VInt((iIndex/iWdt) * iMapZoom - (iMapZoom/2));
// call
pSF->Exec(nullptr, &Pars);
}
// done
}
/* --- C4MCCallbackArrayList --- */
void C4MCCallbackArrayList::Add(C4MCCallbackArray *pNewArray)
{
// add to end
if (pFirst)
{
C4MCCallbackArray *pLast = pFirst;
while (pLast->pNext) pLast=pLast->pNext;
pLast->pNext=pNewArray;
}
else pFirst=pNewArray;
}
void C4MCCallbackArrayList::Clear()
{
// remove all arrays
C4MCCallbackArray *pArray, *pNext=pFirst;
while ((pArray=pNext))
{
pNext=pArray->pNext;
delete pArray;
}
// zero first-field
pFirst=nullptr;
}
void C4MCCallbackArrayList::Execute(int32_t iMapZoom)
{
// execute all arrays
for (C4MCCallbackArray *pArray = pFirst; pArray; pArray=pArray->pNext)
pArray->Execute(iMapZoom);
}
/* --- C4MCNode --- */
C4MCNode::C4MCNode(C4MCNode *pOwner)
{
// reg to owner
Reg2Owner(pOwner);
// no name
*Name=0;
}
C4MCNode::C4MCNode(C4MCParser* pParser, C4MCNode *pOwner, C4MCNode &rTemplate, bool fClone)
{
// Make sure the template is not used recursively within itself
for(C4MCNode* pParent = pOwner; pParent != nullptr; pParent = pParent->Owner)
if(pParent == &rTemplate)
throw C4MCParserErr(pParser, C4MCErr_NoRecTemplate, rTemplate.Name);
// set owner and stuff
Reg2Owner(pOwner);
// copy children from template
for (C4MCNode *pChild=rTemplate.Child0; pChild; pChild=pChild->Next)
pChild->clone(pParser, this);
// no name
*Name=0;
}
C4MCNode::~C4MCNode()
{
// clear
Clear();
// remove from list
if (Prev) Prev->Next = Next; else if (Owner) Owner->Child0 = Next;
if (Next) Next->Prev = Prev; else if (Owner) Owner->ChildL = Prev;
}
void C4MCNode::Reg2Owner(C4MCNode *pOwner)
{
// init list
Child0=ChildL=nullptr;
// owner?
if ((Owner=pOwner))
{
// link into it
if ((Prev = Owner->ChildL))
Prev->Next = this;
else
Owner->Child0 = this;
Owner->ChildL = this;
MapCreator=pOwner->MapCreator;
}
else
{
Prev=nullptr;
MapCreator=nullptr;
}
// we're always last entry
Next=nullptr;
}
void C4MCNode::Clear()
{
// delete all children; they'll unreg themselves
while (Child0) delete Child0;
}
C4MCOverlay *C4MCNode::OwnerOverlay()
{
for (C4MCNode *pOwnr=Owner; pOwnr; pOwnr=pOwnr->Owner)
if (C4MCOverlay *pOwnrOvrl=pOwnr->Overlay())
return pOwnrOvrl;
// no overlay-owner
return nullptr;
}
C4MCNode *C4MCNode::GetNodeByName(const char *szName)
{
// search local list (backwards: last node has highest priority)
for (C4MCNode *pChild=ChildL; pChild; pChild=pChild->Prev)
// name match?
if (SEqual(pChild->Name, szName))
// yeah, success!
return pChild;
// search owner, if present
if (Owner) return Owner->GetNodeByName(szName);
// nothing found
return nullptr;
}
bool C4MCNode::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
{
// no fields in base class
return false;
}
int32_t C4MCNode::IntPar(C4MCParser *pParser, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
{
// check if int32_t
if (ValType == MCT_INT || ValType == MCT_PERCENT || ValType == MCT_PX)
return iVal;
throw C4MCParserErr(pParser, C4MCErr_FieldValInvalid, szSVal);
}
const char *C4MCNode::StrPar(C4MCParser *pParser, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
{
// check if identifier
if (ValType != MCT_IDTF)
throw C4MCParserErr(pParser, C4MCErr_FieldValInvalid, szSVal);
return szSVal;
}
#define IntPar IntPar(pParser, szSVal, iVal, ValType) // shortcut for checked int32_t param
#define StrPar StrPar(pParser, szSVal, iVal, ValType) // shortcut for checked str param
void C4MCNode::ReEvaluate()
{
// evaluate ourselves
Evaluate();
// evaluate children
for (C4MCNode *pChild=Child0; pChild; pChild=pChild->Next)
pChild->ReEvaluate();
}
// overlay
C4MCOverlay::C4MCOverlay(C4MCNode *pOwner) : C4MCNode(pOwner)
{
// zero members
X=Y=Wdt=Hgt=OffX=OffY=0;
Material=MNone;
*Texture=0;
Op=MCT_NONE;
MatClr=0;
MatClrBkg=0;
Algorithm=nullptr;
Sub=false;
ZoomX=ZoomY=0;
FixedSeed=Seed=0;
// Alpha=Beta=0;
Turbulence=Lambda=Rotate=0;
Invert=LooseBounds=Group=Mask=false;
pEvaluateFunc=pDrawFunc=nullptr;
}
C4MCOverlay::C4MCOverlay(C4MCParser* pParser, C4MCNode *pOwner, C4MCOverlay &rTemplate, bool fClone) : C4MCNode(pParser, pOwner, rTemplate, fClone)
{
// copy fields
X=rTemplate.X; Y=rTemplate.Y; Wdt=rTemplate.Wdt; Hgt=rTemplate.Hgt;
RX=rTemplate.RX; RY=rTemplate.RY; RWdt=rTemplate.RWdt; RHgt=rTemplate.RHgt;
OffX=rTemplate.OffX; OffY=rTemplate.OffY; ROffX=rTemplate.ROffX; ROffY=rTemplate.ROffY;
Material=rTemplate.Material;
SCopy(rTemplate.Texture, Texture, C4MaxName);
Algorithm=rTemplate.Algorithm;
Sub=rTemplate.Sub;
ZoomX=rTemplate.ZoomX; ZoomY=rTemplate.ZoomY;
MatClr=rTemplate.MatClr;
MatClrBkg=rTemplate.MatClrBkg;
Seed=rTemplate.Seed;
Alpha=rTemplate.Alpha; Beta=rTemplate.Beta; Turbulence=rTemplate.Turbulence; Lambda=rTemplate.Lambda;
Rotate=rTemplate.Rotate;
Invert=rTemplate.Invert; LooseBounds=rTemplate.LooseBounds; Group=rTemplate.Group; Mask=rTemplate.Mask;
FixedSeed=rTemplate.FixedSeed;
pEvaluateFunc=rTemplate.pEvaluateFunc;
pDrawFunc=rTemplate.pDrawFunc;
// zero non-template-fields
if (fClone) Op=rTemplate.Op; else Op=MCT_NONE;
}
void C4MCOverlay::Default()
{
// default algo
Algorithm=GetAlgo(C4MC_DefAlgo);
// no mat (sky) default
Material=MNone;
*Texture=0;
// but if mat is set, assume it sub
Sub=true;
// full size
OffX=OffY=X=Y=0;
ROffX.Set(0,true); ROffY.Set(0,true); RX.Set(0,true); RY.Set(0,true);
Wdt=Hgt=C4MC_SizeRes;
RWdt.Set(C4MC_SizeRes,true); RHgt.Set(C4MC_SizeRes,true);
// def zoom
ZoomX=ZoomY=C4MC_ZoomRes;
// def values
Alpha.Set(0,false); Beta.Set(0,false); Turbulence=Lambda=Rotate=0; Invert=LooseBounds=Group=Mask=false;
FixedSeed=0;
// script funcs
pEvaluateFunc=pDrawFunc=nullptr;
}
bool C4MCOverlay::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
{
int32_t iMat; C4MCAlgorithm *pAlgo;
// inherited fields
if (C4MCNode::SetField(pParser, szField, szSVal, iVal, ValType)) return true;
//local fields
for (C4MCNodeAttr *pAttr=&C4MCOvrlMap[0]; *pAttr->Name; pAttr++)
if (SEqual(szField, pAttr->Name))
{
// field was found, get offset to store in
MemberAdapter<C4MCOverlay> Target(*this, pAttr->Offset);
// store according to field type
switch (pAttr->Type)
{
case C4MCV_Integer:
// simply store
Target.As<int32_t>() = IntPar;
break;
case C4MCV_Percent:
Target.As<int_bool>().Set(IntPar, ValType == MCT_PERCENT || ValType == MCT_INT);
break;
case C4MCV_Pixels:
Target.As<int_bool>().Set(IntPar, ValType == MCT_PERCENT);
break;
case C4MCV_Material:
// get material by string
iMat = MapCreator->MatMap->Get(StrPar);
// check validity
if (iMat == MNone) throw C4MCParserErr(pParser, C4MCErr_MatNotFound, StrPar);
// store
Target.As<int32_t>() = iMat;
break;
case C4MCV_Texture:
// check validity
if (!MapCreator->TexMap->CheckTexture(StrPar))
throw C4MCParserErr(pParser, C4MCErr_TexNotFound, StrPar);
// store
SCopy(StrPar, Target.As<char [C4M_MaxName + 1]>(), C4M_MaxName);
break;
case C4MCV_Algorithm:
// get algo
pAlgo=GetAlgo(StrPar);
// check validity
if (!pAlgo) throw C4MCParserErr(pParser, C4MCErr_AlgoNotFound, StrPar);
// store
Target.As<C4MCAlgorithm *>()=pAlgo;
break;
case C4MCV_Boolean:
// store whether value is not zero
Target.As<bool>()=IntPar!=0;
break;
case C4MCV_Zoom:
// store calculated zoom
Target.As<int32_t>()=Clamp<int32_t>(C4MC_ZoomRes-IntPar,1,C4MC_ZoomRes*2);
break;
case C4MCV_ScriptFunc:
{
// get script func of main script
C4AulFunc *pSFunc = ::GameScript.ScenPropList._getPropList()->GetFunc(StrPar);
if (!pSFunc) throw C4MCParserErr(pParser, C4MCErr_SFuncNotFound, StrPar);
// add to main
Target.As<C4MCCallbackArray*>() = new C4MCCallbackArray(pSFunc, MapCreator);
}
default:
// TODO
break;
}
// done
return true;
}
// nothing found :(
return false;
}
C4MCAlgorithm *C4MCOverlay::GetAlgo(const char *szName)
{
// search map
for (C4MCAlgorithm *pAlgo = &C4MCAlgoMap[0]; pAlgo->Function; pAlgo++)
// check name
if (SEqual(pAlgo->Identifier, szName))
// success!
return pAlgo;
// nothing found
return nullptr;
}
void C4MCOverlay::Evaluate()
{
// inherited
C4MCNode::Evaluate();
// get mat color
if (Inside<int32_t>(Material,0,MapCreator->MatMap->Num-1))
{
MatClr=MapCreator->TexMap->GetIndexMatTex(MapCreator->MatMap->Map[Material].Name, *Texture ? Texture : nullptr);
if(MatClr == 0 || !Sub)
MatClrBkg = 0;
else
MatClrBkg = Mat2PixColDefault(MTunnel);
}
else
{
MatClr=0;
MatClrBkg=0;
}
// calc size
if (Owner)
{
C4MCOverlay *pOwnrOvrl;
if ((pOwnrOvrl=OwnerOverlay()))
{
int32_t iOwnerWdt=pOwnrOvrl->Wdt; int32_t iOwnerHgt=pOwnrOvrl->Hgt;
X = RX.Evaluate(iOwnerWdt) + pOwnrOvrl->X;
Y = RY.Evaluate(iOwnerHgt) + pOwnrOvrl->Y;
Wdt = RWdt.Evaluate(iOwnerWdt);
Hgt = RHgt.Evaluate(iOwnerHgt);
OffX = ROffX.Evaluate(iOwnerWdt);
OffY = ROffY.Evaluate(iOwnerHgt);
}
}
// calc seed
if (!(Seed=FixedSeed))
{
int32_t r1=Random(32768);
int32_t r2=Random(65536);
Seed=(r1<<16) | r2;
}
}
C4MCOverlay *C4MCOverlay::FirstOfChain()
{
// run backwards until nullptr, non-overlay or overlay without operator is found
C4MCOverlay *pOvrl=this;
C4MCOverlay *pPrevO;
while (pOvrl->Prev)
{
if (!(pPrevO=pOvrl->Prev->Overlay())) break;
if (pPrevO->Op == MCT_NONE) break;
pOvrl=pPrevO;
}
// done
return pOvrl;
}
bool C4MCOverlay::CheckMask(int32_t iX, int32_t iY)
{
// bounds match?
if (!LooseBounds) if (iX<X || iY<Y || iX>=X+Wdt || iY>=Y+Hgt) return false;
if (Config.General.DebugRec)
{
C4RCTrf rc;
rc.x=iX; rc.y=iY; rc.Rotate=Rotate; rc.Turbulence=Turbulence;
AddDbgRec(RCT_MCT1, &rc, sizeof(rc));
}
C4Real dX=itofix(iX); C4Real dY=itofix(iY);
// apply turbulence
if (Turbulence)
{
const C4Real Rad2Grad = itofix(3754936, 65536);
int32_t j=3;
for (int32_t i=10; i<=Turbulence; i*=10)
{
int32_t Seed2; Seed2=Seed;
for (int32_t l=0; l<Lambda+1; ++l)
{
for (C4Real d=itofix(2); d<6; d+=C4REAL10(15))
{
dX += Sin(((dX / 7 + itofix(Seed2) / ZoomX + dY) / j + d) * Rad2Grad) * j / 2;
dY += Cos(((dY / 7 + itofix(Seed2) / ZoomY + dX) / j - d) * Rad2Grad) * j / 2;
}
Seed2 = (Seed * (Seed2<<3) + 0x4465) & 0xffff;
}
j+=3;
}
}
// apply rotation
if (Rotate)
{
C4Real dXo(dX), dYo(dY);
dX = dXo*Cos(itofix(Rotate)) - dYo*Sin(itofix(Rotate));
dY = dYo*Cos(itofix(Rotate)) + dXo*Sin(itofix(Rotate));
}
if (Rotate || Turbulence)
{ iX=fixtoi(dX, ZoomX); iY=fixtoi(dY, ZoomY); }
else
{ iX*=ZoomX; iY*=ZoomY; }
if (Config.General.DebugRec)
{
C4RCPos rc2;
rc2.x=iX; rc2.y=iY;
AddDbgRec(RCT_MCT2, &rc2, sizeof(rc2));
}
// apply offset
iX-=OffX*ZoomX; iY-=OffY*ZoomY;
// check bounds, if loose
if (LooseBounds) if (iX<X*ZoomX || iY<Y*ZoomY || iX>=(X+Wdt)*ZoomX || iY>=(Y+Hgt)*ZoomY) return Invert;
// query algorithm
return (Algorithm->Function) (this, iX, iY)^Invert;
}
bool C4MCOverlay::RenderPix(int32_t iX, int32_t iY, BYTE &rPix, BYTE &rPixBkg, C4MCTokenType eLastOp, bool fLastSet, bool fDraw, C4MCOverlay **ppPixelSetOverlay)
{
// algo match?
bool SetThis=CheckMask(iX, iY);
bool DoSet;
// exec last op
switch (eLastOp)
{
case MCT_AND: // and
DoSet=SetThis&&fLastSet;
break;
case MCT_OR: // or
DoSet=SetThis||fLastSet;
break;
case MCT_XOR: // xor
DoSet=SetThis^fLastSet;
break;
default: // no op
DoSet=SetThis;
break;
}
// set pix to local value and exec children, if no operator is following
if ((DoSet && fDraw && Op == MCT_NONE) || Group)
{
// groups don't set a pixel value, if they're associated with an operator
fDraw &= !Group || (Op == MCT_NONE);
if (fDraw && DoSet && !Mask)
{
rPix=MatClr;
rPixBkg=MatClrBkg;
if (ppPixelSetOverlay) *ppPixelSetOverlay = this;
}
bool fLastSetC=false; eLastOp=MCT_NONE;
// evaluate children overlays, if this was painted, too
for (C4MCNode *pChild=Child0; pChild; pChild=pChild->Next)
if (C4MCOverlay *pOvrl=pChild->Overlay())
{
fLastSetC=pOvrl->RenderPix(iX, iY, rPix, rPixBkg, eLastOp, fLastSetC, fDraw, ppPixelSetOverlay);
if (Group && (pOvrl->Op == MCT_NONE))
DoSet |= fLastSetC;
eLastOp=pOvrl->Op;
}
// add evaluation-callback
if (pEvaluateFunc && DoSet && fDraw) pEvaluateFunc->EnablePixel(iX, iY);
}
// done
return DoSet;
}
bool C4MCOverlay::PeekPix(int32_t iX, int32_t iY)
{
// start with this one
C4MCOverlay *pOvrl=this; bool fLastSetC=false; C4MCTokenType eLastOp=MCT_NONE; BYTE Crap;
// loop through op chain
while (true)
{
fLastSetC=pOvrl->RenderPix(iX, iY, Crap, Crap, eLastOp, fLastSetC, false);
eLastOp=pOvrl->Op;
if (!pOvrl->Op) break;
// must be another overlay, since there's an operator
// hopefully, the preparser will catch all the other crap
pOvrl=pOvrl->Next->Overlay();
}
// return result
return fLastSetC;
}
// point
C4MCPoint::C4MCPoint(C4MCNode *pOwner) : C4MCNode(pOwner)
{
// zero members
X=Y=0;
}
C4MCPoint::C4MCPoint(C4MCParser* pParser, C4MCNode *pOwner, C4MCPoint &rTemplate, bool fClone) : C4MCNode(pParser, pOwner, rTemplate, fClone)
{
// copy fields
X=rTemplate.X; Y=rTemplate.Y;
RX=rTemplate.RX; RY=rTemplate.RY;
}
void C4MCPoint::Default()
{
X=Y=0;
}
bool C4MCPoint::SetField(C4MCParser *pParser, const char *szField, const char *szSVal, int32_t iVal, C4MCTokenType ValType)
{
// only explicit %/px
if (ValType == MCT_INT) return false;
if (SEqual (szField, "x"))
{
RX.Set(IntPar, ValType == MCT_PERCENT);
return true;
}
else if (SEqual (szField, "y"))
{
RY.Set(IntPar, ValType == MCT_PERCENT);
return true;
}
return false;
}
void C4MCPoint::Evaluate()
{
// inherited
C4MCNode::Evaluate();
// get mat color
// calc size
if (Owner)
{
C4MCOverlay *pOwnrOvrl;
if ((pOwnrOvrl=OwnerOverlay()))
{
X = RX.Evaluate(pOwnrOvrl->Wdt) + pOwnrOvrl->X;
Y = RY.Evaluate(pOwnrOvrl->Hgt) + pOwnrOvrl->Y;
}
}
}
// map
C4MCMap::C4MCMap(C4MCNode *pOwner) : C4MCOverlay(pOwner)
{
}
C4MCMap::C4MCMap(C4MCParser* pParser, C4MCNode *pOwner, C4MCMap &rTemplate, bool fClone) : C4MCOverlay(pParser, pOwner, rTemplate, fClone)
{
}
void C4MCMap::Default()
{
// inherited
C4MCOverlay::Default();
// size by landscape def
MapCreator->Landscape->GetMapSize(Wdt, Hgt, MapCreator->PlayerCount);
}
bool C4MCMap::RenderTo(BYTE *pToBuf, BYTE *pToBufBkg, int32_t iPitch)
{
// set current render target
if (MapCreator) MapCreator->pCurrentMap=this;
// draw pixel by pixel
for (int32_t iY=0; iY<Hgt; iY++)
{
for (int32_t iX=0; iX<Wdt; iX++)
{
// default to sky
BYTE dummyPix;
*pToBuf=0;
if (pToBufBkg) *pToBufBkg=0;
// render pixel value
C4MCOverlay *pRenderedOverlay = nullptr;
RenderPix(iX, iY, *pToBuf, pToBufBkg ? *pToBufBkg : dummyPix, MCT_NONE, false, true, &pRenderedOverlay);
// add draw-callback for rendered overlay
if (pRenderedOverlay)
if (pRenderedOverlay->pDrawFunc)
pRenderedOverlay->pDrawFunc->EnablePixel(iX, iY);
// next pixel
pToBuf++;
if (pToBufBkg) pToBufBkg++;
}
// next line
pToBuf+=iPitch-Wdt;
if (pToBufBkg) pToBufBkg+=iPitch-Wdt;
}
// reset render target
if (MapCreator) MapCreator->pCurrentMap=nullptr;
// success
return true;
}
void C4MCMap::SetSize(int32_t iWdt, int32_t iHgt)
{
// store new size
Wdt=iWdt; Hgt=iHgt;
// update relative values
MapCreator->ReEvaluate();
}
// map creator
C4MapCreatorS2::C4MapCreatorS2(C4SLandscape *pLandscape, C4TextureMap *pTexMap, C4MaterialMap *pMatMap, int iPlayerCount) : C4MCNode(nullptr)
{
// me r b creator
MapCreator=this;
// store members
Landscape=pLandscape; TexMap=pTexMap; MatMap=pMatMap;
PlayerCount=iPlayerCount;
// set engine field for default stuff
DefaultMap.MapCreator=this;
DefaultOverlay.MapCreator=this;
DefaultPoint.MapCreator=this;
// default to landscape settings
Default();
}
C4MapCreatorS2::~C4MapCreatorS2()
{
// clear fields
Clear();
}
void C4MapCreatorS2::Default()
{
// default templates
DefaultMap.Default();
DefaultOverlay.Default();
DefaultPoint.Default();
pCurrentMap=nullptr;
}
void C4MapCreatorS2::Clear()
{
// clear nodes
C4MCNode::Clear();
// clear callbacks
CallbackArrays.Clear();
// defaults templates
Default();
}
bool C4MapCreatorS2::ReadFile(const char *szFilename, C4Group *pGrp)
{
// create parser and read file
try
{
C4MCParser(this).ParseFile(szFilename, pGrp);
}
catch (C4MCParserErr err)
{
err.show();
return false;
}
// success
return true;
}
bool C4MapCreatorS2::ReadScript(const char *szScript)
{
// create parser and read
try
{
C4MCParser(this).Parse(szScript);
}
catch (C4MCParserErr err)
{
err.show();
return false;
}
// success
return true;
}
C4MCMap *C4MapCreatorS2::GetMap(const char *szMapName)
{
C4MCMap *pMap=nullptr; C4MCNode *pNode;
// get map
if (szMapName && *szMapName)
{
// by name...
if ((pNode = GetNodeByName(szMapName)))
if (pNode->Type() == MCN_Map)
pMap = (C4MCMap *) pNode;
}
else
{
// or simply last map entry
for (pNode = ChildL; pNode; pNode=pNode->Prev)
if (pNode->Type() == MCN_Map)
{
pMap = (C4MCMap *) pNode;
break;
}
}
return pMap;
}
bool C4MapCreatorS2::Render(const char *szMapName, CSurface8*& sfcMap, CSurface8*& sfcMapBkg)
{
assert(sfcMap == nullptr);
assert(sfcMapBkg == nullptr);
// get map
C4MCMap *pMap=GetMap(szMapName);
if (!pMap) return false;
// get size
int32_t sfcWdt, sfcHgt;
sfcWdt=pMap->Wdt; sfcHgt=pMap->Hgt;
if (!sfcWdt || !sfcHgt) return false;
// create surfaces
sfcMap = new CSurface8(sfcWdt, sfcHgt);
sfcMapBkg = new CSurface8(sfcWdt, sfcHgt);
assert(sfcMap->Pitch == sfcMapBkg->Pitch);
// render map to surface
pMap->RenderTo(sfcMap->Bits, sfcMapBkg->Bits, sfcMap->Pitch);
// success
return true;
}
static inline void DWordAlign(int &val)
{
if (val%4) { val>>=2; val<<=2; val+=4; }
}
BYTE *C4MapCreatorS2::RenderBuf(const char *szMapName, int32_t &sfcWdt, int32_t &sfcHgt)
{
// get map
C4MCMap *pMap=GetMap(szMapName);
if (!pMap) return nullptr;
// get size
sfcWdt=pMap->Wdt; sfcHgt=pMap->Hgt;
if (!sfcWdt || !sfcHgt) return nullptr;
int dwSfcWdt = sfcWdt;
DWordAlign(dwSfcWdt);
sfcWdt = dwSfcWdt;
// create buffer
BYTE *buf=new BYTE[sfcWdt*sfcHgt];
// render and return it
pMap->RenderTo(buf, nullptr, sfcWdt);
return buf;
}
C4MCParserErr::C4MCParserErr(C4MCParser *pParser, const char *szMsg)
{
// create error message
sprintf(Msg, "%s: %s (%d)", pParser->Filename, szMsg, pParser->BPos ? SGetLine(pParser->BPos, pParser->CPos) : 0);
}
C4MCParserErr::C4MCParserErr(C4MCParser *pParser, const char *szMsg, const char *szPar)
{
char Buf[C4MaxMessage];
// create error message
sprintf(Buf, szMsg, szPar);
sprintf(Msg, "%s: %s (%d)", pParser->Filename, Buf, pParser->BPos ? SGetLine(pParser->BPos, pParser->CPos) : 0);
}
void C4MCParserErr::show()
{
// log error
Log(Msg);
}
// parser
C4MCParser::C4MCParser(C4MapCreatorS2 *pMapCreator)
{
// store map creator
MapCreator=pMapCreator;
// reset some fields
Code=nullptr; BPos = nullptr; CPos=nullptr; *Filename=0;
}
C4MCParser::~C4MCParser()
{
// clean up
Clear();
}
void C4MCParser::Clear()
{
// clear code if present
delete [] Code; Code=nullptr; BPos = nullptr; CPos=nullptr;
// reset filename
*Filename=0;
}
bool C4MCParser::AdvanceSpaces()
{
char C, C2 = (char) 0;
// defaultly, not in comment
int32_t InComment = 0; // 0/1/2 = no comment/line comment/multi line comment
// don't go past end
while ((C = *CPos))
{
// loop until out of comment and non-whitespace is found
switch (InComment)
{
case 0:
if (C == '/')
{
CPos++;
switch (*CPos)
{
case '/': InComment = 1; break;
case '*': InComment = 2; break;
default: CPos--; return true;
}
}
else if ((BYTE) C > 32) return true;
break;
case 1:
if (((BYTE) C == 13) || ((BYTE) C == 10)) InComment = 0;
break;
case 2:
if ((C == '/') && (C2 == '*')) InComment = 0;
break;
}
// next char; store prev
CPos++; C2 = C;
}
// end of code reached; return false
return false;
}
bool C4MCParser::GetNextToken()
{
// move to start of token
if (!AdvanceSpaces()) { CurrToken=MCT_EOF; return false; }
// store offset
const char *CPos0 = CPos;
int32_t Len = 0;
// token get state
enum TokenGetState
{
TGS_None, // just started
TGS_Ident, // getting identifier
TGS_Int, // getting integer
TGS_Dir // getting directive
};
TokenGetState State = TGS_None;
// loop until finished
while (true)
{
// get char
char C = *CPos;
switch (State)
{
case TGS_None:
// get token type by first char
// +/- are operators
if ((((C >= '0') && (C <= '9')) || (C == '+') || (C == '-')))
State = TGS_Int; // integer by +, -, 0-9
else if (C == '#') State = TGS_Dir; // directive by "#"
else if (C == ';') {CPos++; CurrToken=MCT_SCOLON; return true; } // ";"
else if (C == '=') {CPos++; CurrToken=MCT_EQ; return true; } // "="
else if (C == '{') {CPos++; CurrToken=MCT_BLOPEN; return true; } // "{"
else if (C == '}') {CPos++; CurrToken=MCT_BLCLOSE; return true; } // "}"
else if (C == '&') {CPos++; CurrToken=MCT_AND; return true; } // "&"
else if (C == '|') {CPos++; CurrToken=MCT_OR; return true; } // "|"
else if (C == '^') {CPos++; CurrToken=MCT_XOR; return true; } // "^"
else if (C >= '@') State = TGS_Ident; // identifier by all non-special chars
else
{
// unrecognized char
CPos++;
throw C4MCParserErr(this, "unexpected character found");
}
break;
case TGS_Ident: // ident and directive: parse until non ident-char is found
case TGS_Dir:
if (((C < '0') || (C > '9')) && ((C < 'a') || (C > 'z')) && ((C < 'A') || (C > 'Z')) && (C != '_'))
{
// return ident/directive
Len = std::min<int32_t>(Len, C4MaxName);
SCopy(CPos0, CurrTokenIdtf, Len);
if (State==TGS_Ident) CurrToken=MCT_IDTF; else CurrToken=MCT_DIR;
return true;
}
break;
case TGS_Int: // integer: parse until non-number is found
if ((C < '0') || (C > '9'))
{
// return integer
Len = std::min<int32_t>(Len, C4MaxName);
CurrToken=MCT_INT;
// check for "-"
if (Len == 1 && *CPos0 == '-')
{
CurrToken = MCT_RANGE;
return true;
}
else if ('%' == C) { CPos++; CurrToken=MCT_PERCENT; } // "%"
else if ('p' == C)
{
// p or px
++CPos;
if ('x' == *CPos) ++CPos;
CurrToken=MCT_PX;
}
SCopy(CPos0, CurrTokenIdtf, Len);
// it's not, so return the int32_t
sscanf(CurrTokenIdtf, "%d", &CurrTokenVal);
return true;
}
break;
}
// next char
CPos++; Len++;
}
}
static void PrintNodeTree(C4MCNode *pNode, int depth)
{
for (int i = 0; i < depth; ++i)
printf(" ");
switch (pNode->Type())
{
case MCN_Node: printf("Node %s\n", pNode->Name); break;
case MCN_Overlay: printf("Overlay %s\n", pNode->Name); break;
case MCN_Point: printf("Point %s\n", pNode->Name); break;
case MCN_Map: printf("Map %s\n", pNode->Name); break;
}
for (C4MCNode * pChild = pNode->Child0; pChild; pChild = pChild->Next)
PrintNodeTree(pChild, depth + 1);
}
void C4MCParser::ParseTo(C4MCNode *pToNode)
{
C4MCNode *pNewNode=nullptr; // new node
bool Done=false; // finished?
C4MCNodeType LastOperand = C4MCNodeType(-1); // last first operand of operator
char FieldName[C4MaxName];// buffer for current field to access
C4MCNode *pCpyNode; // node to copy from
// current state
enum ParseState
{
PS_NONE, // just started
PS_KEYWD1, // got block-opening keyword (map, overlay etc.)
PS_KEYWD1N, // got name for block
PS_AFTERNODE, // node has been parsed; expect ; or operator
PS_GOTOP, // got operator
PS_GOTIDTF, // got identifier, expect '=', ';' or '{'; identifier remains in CurrTokenIdtf
PS_GOTOPIDTF, // got identifier after operator; accept ';' or '{' only
PS_SETFIELD // accept field value; field is stored in FieldName
};
ParseState State = PS_NONE;
// parse until end of file (or block)
while (GetNextToken())
{
switch (State)
{
case PS_NONE:
case PS_GOTOP:
switch (CurrToken)
{
case MCT_DIR:
// top level needed
if (!pToNode->GlobalScope())
throw C4MCParserErr(this, C4MCErr_NoDirGlobal);
// no directives so far
throw C4MCParserErr(this, C4MCErr_UnknownDir, CurrTokenIdtf);
break;
case MCT_IDTF:
// identifier: check keywords
if (SEqual(CurrTokenIdtf, C4MC_Overlay))
{
// overlay: create overlay node, using default template
pNewNode = new C4MCOverlay(this, pToNode, MapCreator->DefaultOverlay, false);
State=PS_KEYWD1;
}
else if (SEqual(CurrTokenIdtf, C4MC_Point) && !pToNode->GetNodeByName(CurrTokenIdtf))
{
// only in overlays
if (!pToNode->Overlay())
throw C4MCParserErr(this, C4MCErr_PointOnlyOvl);
// create point node, using default template
pNewNode = new C4MCPoint(this, pToNode, MapCreator->DefaultPoint, false);
State=PS_KEYWD1;
}
else if (SEqual(CurrTokenIdtf, C4MC_Map))
{
// map: check top level
if (!pToNode->GlobalScope())
throw C4MCParserErr(this, C4MCErr_MapNoGlobal);
// create map node, using default template
pNewNode = new C4MCMap(this, pToNode, MapCreator->DefaultMap, false);
State=PS_KEYWD1;
}
else
{
// so this is either a field-set or a defined node
// '=', ';' or '{' may follow, none of these will clear the CurrTokenIdtf
// so safely assume it preserved and just update the state
if (State==PS_GOTOP) State=PS_GOTOPIDTF; else State=PS_GOTIDTF;
}
// operator: check type
if (State == PS_GOTOP && pNewNode)
if (LastOperand != pNewNode->Type())
throw C4MCParserErr(this, C4MCErr_OpTypeErr);
break;
case MCT_BLCLOSE:
case MCT_EOF:
// block done
Done=true;
break;
default:
// we don't like that
throw C4MCParserErr(this, C4MCErr_IdtfExp);
break;
}
break;
case PS_KEYWD1:
if (CurrToken==MCT_IDTF)
{
// name the current node
SCopy(CurrTokenIdtf, pNewNode->Name, C4MaxName);
State=PS_KEYWD1N;
break;
}
else if (pToNode->GlobalScope())
{
// disallow unnamed nodes in global scope
throw C4MCParserErr(this, C4MCErr_UnnamedNoGlbl);
}
// in local scope, allow unnamed; so continue
case PS_KEYWD1N:
// do expect a block opening
if (CurrToken!=MCT_BLOPEN)
throw C4MCParserErr(this, C4MCErr_BlOpenExp);
// parse new node
ParseTo(pNewNode);
// check file end
if (CurrToken==MCT_EOF)
throw C4MCParserErr(this, C4MCErr_EOF);
// reset state
State=PS_AFTERNODE;
break;
case PS_GOTIDTF:
case PS_GOTOPIDTF:
switch (CurrToken)
{
case MCT_EQ:
// so it's a field set
// not after operators
if (State==PS_GOTOPIDTF)
throw C4MCParserErr(this, C4MCErr_Obj2Exp);
// store field name
SCopy(CurrTokenIdtf, FieldName, C4MaxName);
// update state to accept value
State=PS_SETFIELD;
break;
case MCT_BLOPEN:
case MCT_SCOLON:
case MCT_AND: case MCT_OR: case MCT_XOR:
// so it's a node copy
// local scope only
if (pToNode->GlobalScope())
throw C4MCParserErr(this, C4MCErr_ReinstNoGlobal, CurrTokenIdtf);
// get the node
pCpyNode=pToNode->GetNodeByName(CurrTokenIdtf);
if (!pCpyNode)
throw C4MCParserErr(this, C4MCErr_UnknownObj, CurrTokenIdtf);
// create the copy
switch (pCpyNode->Type())
{
case MCN_Overlay:
// create overlay
pNewNode=new C4MCOverlay(this, pToNode, *((C4MCOverlay *) pCpyNode), false);
break;
case MCN_Map:
// maps not allowed
if (pCpyNode->Type() == MCN_Map)
throw C4MCParserErr(this, C4MCErr_MapNoGlobal, CurrTokenIdtf);
break;
default:
// huh?
throw C4MCParserErr(this, C4MCErr_ReinstUnknown, CurrTokenIdtf);
break;
}
// check type for operators
if (State==PS_GOTOPIDTF)
if (LastOperand != pNewNode->Type())
throw C4MCParserErr(this, C4MCErr_OpTypeErr);
// further overloads?
if (CurrToken==MCT_BLOPEN)
{
// parse new node
ParseTo(pNewNode);
// get next token, as we'll simply fall through to PS_AFTERNODE
GetNextToken();
// check file end
if (CurrToken==MCT_EOF)
throw C4MCParserErr(this, C4MCErr_EOF);
}
// reset state
State=PS_AFTERNODE;
break;
default:
throw C4MCParserErr(this, C4MCErr_EqSColonBlOpenExp);
break;
}
// fall through to next case, if it was a named node reinstanciation
if (State != PS_AFTERNODE) break;
case PS_AFTERNODE:
// expect operator or semicolon
switch (CurrToken)
{
case MCT_SCOLON:
// reset state
State=PS_NONE;
break;
case MCT_AND:
case MCT_OR:
case MCT_XOR:
// operator: not in global scope
if (pToNode->GlobalScope())
throw C4MCParserErr(this, C4MCErr_OpsNoGlobal);
// set operator
if (!pNewNode->SetOp(CurrToken))
throw C4MCParserErr(this, "';' expected");
LastOperand=pNewNode->Type();
// update state
State=PS_GOTOP;
break;
default:
throw C4MCParserErr(this, C4MCErr_SColonOrOpExp);
break;
}
// node done
// evaluate node and children, if this is top-level
// we mustn't evaluate everything immediately, because parents must be evaluated first!
if (pToNode->GlobalScope()) pNewNode->ReEvaluate();
pNewNode=nullptr;
break;
case PS_SETFIELD:
ParseValue (pToNode, FieldName);
/*// set field: accept integer constants and identifiers
switch (CurrToken)
{
case MCT_IDTF:
// reset value field
CurrTokenVal=0;
case MCT_INT:
break;
default:
throw C4MCParserErr(this, C4MCErr_FieldConstExp, CurrTokenIdtf);
break;
}
// set field
if (!pToNode->SetField(this, FieldName, CurrTokenIdtf, CurrTokenVal, CurrToken))
// field not found
throw C4MCParserErr(this, C4MCErr_Field404, FieldName);
// now, the one and only thing to get is a semicolon
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
if (CurrToken != MCT_SCOLON)
throw C4MCParserErr(this, C4MCErr_SColonExp);*/
// reset state
State=PS_NONE;
break;
}
// don't get another token!
if (Done) break;
}
// end of file expected?
if (State != PS_NONE)
{
if (State == PS_GOTOP)
throw C4MCParserErr(this, C4MCErr_Obj2Exp);
else
throw C4MCParserErr(this, C4MCErr_EOF);
}
}
void C4MCParser::ParseValue(C4MCNode *pToNode, const char *szFieldName)
{
int32_t Value;
C4MCTokenType Type;
switch (CurrToken)
{
case MCT_IDTF:
{
// set field
if (!pToNode->SetField(this, szFieldName, CurrTokenIdtf, 0, CurrToken))
// field not found
throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
break;
}
case MCT_INT:
case MCT_PX:
case MCT_PERCENT:
{
Value = CurrTokenVal;
Type = CurrToken;
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
// range
if (MCT_RANGE == CurrToken)
{
// Get the second value
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
if (MCT_INT == CurrToken || MCT_PX == CurrToken || MCT_PERCENT == CurrToken)
{
Value += Random (CurrTokenVal - Value);
}
else
throw C4MCParserErr(this, C4MCErr_FieldConstExp, CurrTokenIdtf);
Type = CurrToken;
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
}
if (!pToNode->SetField(this, szFieldName, CurrTokenIdtf, Value, Type))
// field not found
throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
break;
}
default:
{
throw C4MCParserErr(this, C4MCErr_FieldConstExp, CurrTokenIdtf);
}
}
// now, the one and only thing to get is a semicolon
if (CurrToken != MCT_SCOLON)
throw C4MCParserErr(this, C4MCErr_SColonExp);
/*
// set field: accept integer constants and identifiers
switch (CurrToken)
{
case MCT_IDTF:
// reset value field
CurrTokenVal=0;
// set field
if (!pToNode->SetField(this, szFieldName, CurrTokenIdtf, CurrTokenVal, CurrToken))
// field not found
throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
break;
case MCT_INT:
Value1 = CurrTokenVal;
while (GetNextToken ())
{
switch (CurrToken)
{
case MCT_SCOLON:
// set field
if (!pToNode->SetField(this, szFieldName, CurrTokenIdtf, Value1, MCT_INT))
// field not found
throw C4MCParserErr(this, C4MCErr_Field404, szFieldName);
return;
break;
case MCT_RANGE:
break;
case MCT_INT:
Value2 = CurrTokenVal;
Value1 += Random (Value2 - Value1);
break;
default:
throw C4MCParserErr(this, C4MCErr_SColonExp);
break;
}
}
break;
default:
throw C4MCParserErr(this, C4MCErr_FieldConstExp, CurrTokenIdtf);
break;
}
// now, the one and only thing to get is a semicolon
if (!GetNextToken())
throw C4MCParserErr(this, C4MCErr_EOF);
if (CurrToken != MCT_SCOLON)
throw C4MCParserErr(this, C4MCErr_SColonExp);*/
}
void C4MCParser::ParseFile(const char *szFilename, C4Group *pGrp)
{
size_t iSize; // file size
// clear any old data
Clear();
// store filename
SCopy(szFilename, Filename, C4MaxName);
// check group
if (!pGrp) throw C4MCParserErr(this, C4MCErr_NoGroup);
// get file
if (!pGrp->AccessEntry(szFilename, &iSize))
// 404
throw C4MCParserErr(this, C4MCErr_404);
// file is empty?
if (!iSize) return;
// alloc mem
Code = new char[iSize+1];
// read file
pGrp->Read((void *) Code, iSize);
Code[iSize]=0;
// parse it
BPos=Code;
CPos=Code;
ParseTo(MapCreator);
if (false) PrintNodeTree(MapCreator, 0);
// free code
// on errors, this will be done be destructor
Clear();
}
void C4MCParser::Parse(const char *szScript)
{
// clear any old data
Clear();
// parse it
BPos=szScript;
CPos=szScript;
ParseTo(MapCreator);
if (false) PrintNodeTree(MapCreator, 0);
// free code
// on errors, this will be done be destructor
Clear();
}
void C4MCParser::ParseMemFile(const char *szScript, const char *szFilename)
{
// clear any old data
Clear();
// store filename
SCopy(szFilename, Filename, C4MaxName);
// parse it
BPos=szScript;
CPos=szScript;
ParseTo(MapCreator);
// on errors, this will be done be destructor
Clear();
}
// algorithms ---------------------
// helper func
bool PreparePeek(C4MCOverlay **ppOvrl, int32_t &iX, int32_t &iY, C4MCOverlay **ppTopOvrl)
{
// zoom out
iX/=(*ppOvrl)->ZoomX; iY/=(*ppOvrl)->ZoomY;
// get owning overlay
C4MCOverlay *pOvrl2=(*ppOvrl)->OwnerOverlay();
if (!pOvrl2) return false;
// get uppermost overlay
C4MCOverlay *pNextOvrl;
for (*ppTopOvrl=pOvrl2; (pNextOvrl=(*ppTopOvrl)->OwnerOverlay()); *ppTopOvrl=pNextOvrl) {}
// get first of operator-chain
pOvrl2=pOvrl2->FirstOfChain();
// set new overlay
*ppOvrl=pOvrl2;
// success
return true;
}
#define a pOvrl->Alpha
#define b pOvrl->Beta
#define s pOvrl->Seed
#define z C4MC_ZoomRes
#define z2 (C4MC_ZoomRes*C4MC_ZoomRes)
bool AlgoSolid(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// solid always solid :)
return true;
}
bool AlgoRandom(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// totally random
return !((((s ^ (iX<<2) ^ (iY<<5))^((s>>16)+1+iX+(iY<<2)))/17)%(a.Evaluate(C4MC_SizeRes)+2));
}
bool AlgoChecker(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// checkers with size of 10
return !(((iX/(z*10))%2)^((iY/(z*10))%2));
}
bool AlgoBozo(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// do some bozo stuff - keep it regular here, since it may be modified by turbulence
int32_t iXC=(iX/10+s+(iY/80))%(z*2)-z;
int32_t iYC=(iY/10+s+(iX/80))%(z*2)-z;
int32_t id=Abs(iXC*iYC); // ((iSeed^iX^iY)%z)
return id > z2*(a.Evaluate(C4MC_SizeRes)+10)/50;
}
bool AlgoSin(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// a sine curve where bottom is filled
return iY > fixtoi((Sin(itofix(iX/z*10))+1)*z*10);
}
bool AlgoBoxes(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// For percents instead of Pixels
int32_t pxb = b.Evaluate(pOvrl->Wdt);
int32_t pxa = a.Evaluate(pOvrl->Wdt);
// return whether inside box
return Abs(iX+(s%4738))%(pxb*z+1)<pxa*z+1 && Abs(iY+(s/4738))%(pxb*z+1)<pxa*z+1;
}
bool AlgoRndChecker(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// randomly set squares with size of 10
return AlgoRandom(pOvrl, iX/(z*10), iY/(z*10));
}
bool AlgoLines(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// For percents instead of Pixels
int32_t pxb = b.Evaluate(pOvrl->Wdt);
int32_t pxa = a.Evaluate(pOvrl->Wdt);
// return whether inside line
return Abs(iX+(s%4738))%(pxb*z+1)<pxa*z+1;
}
bool AlgoBorder(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
C4MCOverlay *pTopOvrl;
// get params before, since pOvrl will be changed by PreparePeek
int32_t la=a.Evaluate(pOvrl->Wdt); int32_t lb=b.Evaluate(pOvrl->Hgt);
// prepare a pixel peek from owner
if (!PreparePeek(&pOvrl, iX, iY, &pTopOvrl)) return false;
// query a/b pixels in x/y-directions
for (int32_t x=iX-la; x<=iX+la; x++) if (pTopOvrl->InBounds(x, iY)) if (!pOvrl->PeekPix(x, iY)) return true;
for (int32_t y=iY-lb; y<=iY+lb; y++) if (pTopOvrl->InBounds(iX, y)) if (!pOvrl->PeekPix(iX, y)) return true;
// nothing found
return false;
}
bool AlgoMandel(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// how many iterations?
uint32_t iMandelIter = a.Evaluate(C4MC_SizeRes) != 0 ? a.Evaluate(C4MC_SizeRes) : 1000;
if (iMandelIter < 10) iMandelIter = 10;
// calc c & ci values
double c = ((double) iX / z / pOvrl->Wdt - .5 * ((double) pOvrl->ZoomX / z)) * 4;
double ci = ((double) iY / z / pOvrl->Hgt - .5 * ((double) pOvrl->ZoomY / z)) * 4;
// create _z & _zi
double _z = c, _zi = ci;
double xz;
uint32_t i;
for (i=0; i<iMandelIter; i++)
{
xz = _z * _z - _zi * _zi;
_zi = 2 * _z * _zi + ci;
_z = xz + c;
if (_z * _z + _zi * _zi > 4) break;
}
return !(i<iMandelIter);
}
bool AlgoGradient(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
return (std::abs((iX^(iY*3)) * 2531011L) % 214013L) % z > iX / pOvrl->Wdt;
}
bool AlgoScript(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
C4AulParSet Pars(iX, iY, pOvrl->Alpha.Evaluate(C4MC_SizeRes), pOvrl->Beta.Evaluate(C4MC_SizeRes));
return ::GameScript.Call(FormatString("ScriptAlgo%s", pOvrl->Name).getData(), &Pars).getBool();
}
bool AlgoRndAll(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// return by seed and params; ignore pos
return s%100<a.Evaluate(C4MC_SizeRes);
}
bool AlgoPolygon(C4MCOverlay *pOvrl, int32_t iX, int32_t iY)
{
// Wo do not support empty polygons.
if (!pOvrl -> ChildL) return false;
int32_t uX = 0; // last point before current point
int32_t uY = 0; // with uY != iY
int32_t cX, cY; // current point
int32_t lX = 0; // x of really last point before current point
int32_t count = 0;
bool ignore = false;
int32_t zX; //Where edge intersects with line
C4MCNode *pChild, *pStartChild;
//get a point with uY!=iY, or anyone
for (pChild = pOvrl -> ChildL; pChild->Prev; pChild = pChild->Prev)
if (pChild->Type() == MCN_Point)
{
uX = ((C4MCPoint*) pChild) -> X * 100;
lX = uX;
uY = ((C4MCPoint*) pChild) -> Y * 100;
if (iY != uY) break;
}
pStartChild = pChild -> Next;
if (!pStartChild) pStartChild = pOvrl->Child0;
if (!pStartChild) return false;
for (pChild = pStartChild; ; pChild=pChild -> Next)
{
if (!pChild) pChild = pOvrl->Child0;
if (pChild->Type() == MCN_Point)
{
cX = ((C4MCPoint*) pChild) -> X * 100;
cY = ((C4MCPoint*) pChild) -> Y * 100;
//If looking at line
if (ignore)
{
//if C is on line
if (cY == iY)
{
//if I is on edge
if (((lX < iX) == (iX < cX)) || (cX == iX)) return true;
}
else
{
//if edge intersects line
if ((uY < iY) == (iY < cY) && (lX >= iX)) count++;
ignore = false;
uX = cX;
uY = cY;
}
}
//if looking at ray
else
{
//If point C lays on ray
if (cY == iY)
{
//are I and C the same points?
if (cX == iX) return true;
//skip this point for now
ignore = true;
}
else
{
//if edge intersects line
if ((uY < iY) == (iY <= cY))
{
//and edge intersects ray, because both points are right of iX
if (iX < std::min (uX, cX))
{
count++;
}
//or one is right of I
else if (iX <= std::max (uX, cX))
{
//and edge intersects with ray
if (iX < (zX = ((cX - uX) * (iY - uY) / (cY - uY)) + uX)) count++;
//if I lays on CU
if (zX == iX) return true;
}
}
uX = cX;
uY = cY;
}
}
lX = cX;
}
if (pChild -> Next == pStartChild) break;
if (!pChild -> Next) if (pStartChild == pOvrl->Child0) break;
}
//if edge has changed side of ray uneven times
if ((count & 1) > 0) return true; else return false;
}
#undef a
#undef b
#undef s
#undef z
#undef z2
C4MCAlgorithm C4MCAlgoMap[] =
{
{ "solid", &AlgoSolid },
{ "random", &AlgoRandom },
{ "checker", &AlgoChecker },
{ "bozo", &AlgoBozo },
{ "sin", &AlgoSin },
{ "boxes", &AlgoBoxes },
{ "rndchecker", &AlgoRndChecker },
{ "lines", &AlgoLines },
{ "border", &AlgoBorder },
{ "mandel", &AlgoMandel },
{ "gradient", &AlgoGradient },
{ "script", &AlgoScript },
{ "rndall", &AlgoRndAll },
{ "poly", &AlgoPolygon },
{ "", nullptr }
};
#define offsC4MCOvrl(x) reinterpret_cast<C4MCOverlayOffsetType>(&C4MCOverlay::x)
namespace {
C4MCNodeAttr C4MCOvrlMap[] =
{
{ "x", C4MCV_Percent, offsC4MCOvrl(RX) },
{ "y", C4MCV_Percent, offsC4MCOvrl(RY) },
{ "wdt", C4MCV_Percent, offsC4MCOvrl(RWdt) },
{ "hgt", C4MCV_Percent, offsC4MCOvrl(RHgt) },
{ "ox", C4MCV_Percent, offsC4MCOvrl(ROffX) },
{ "oy", C4MCV_Percent, offsC4MCOvrl(ROffY) },
{ "mat", C4MCV_Material, offsC4MCOvrl(Material) },
{ "tex", C4MCV_Texture, offsC4MCOvrl(Texture) },
{ "algo", C4MCV_Algorithm, offsC4MCOvrl(Algorithm) },
{ "sub", C4MCV_Boolean, offsC4MCOvrl(Sub) },
{ "zoomX", C4MCV_Zoom, offsC4MCOvrl(ZoomX) },
{ "zoomY", C4MCV_Zoom, offsC4MCOvrl(ZoomY) },
{ "a", C4MCV_Pixels, offsC4MCOvrl(Alpha) },
{ "b", C4MCV_Pixels, offsC4MCOvrl(Beta) },
{ "turbulence", C4MCV_Integer, offsC4MCOvrl(Turbulence) },
{ "lambda", C4MCV_Integer, offsC4MCOvrl(Lambda) },
{ "rotate", C4MCV_Integer, offsC4MCOvrl(Rotate) },
{ "seed", C4MCV_Integer, offsC4MCOvrl(FixedSeed) },
{ "invert", C4MCV_Boolean, offsC4MCOvrl(Invert) },
{ "loosebounds", C4MCV_Boolean, offsC4MCOvrl(LooseBounds) },
{ "grp", C4MCV_Boolean, offsC4MCOvrl(Group) },
{ "mask", C4MCV_Boolean, offsC4MCOvrl(Mask) },
{ "evalFn", C4MCV_ScriptFunc, offsC4MCOvrl(pEvaluateFunc) },
{ "drawFn", C4MCV_ScriptFunc, offsC4MCOvrl(pDrawFunc) },
{ "", C4MCV_None, nullptr }
};
}