void C4MapScriptMatTexMask::Init(const C4Value &spec) { // Mask may be initialized by a simple string or by an array of strings, of which the effects are OR'ed const C4ValueArray *arr = spec.getArray(); if (arr) { // Init by array for (int32_t i=0; i<arr->GetSize(); ++i) { C4String *smask = arr->GetItem(i).getStr(); if (!smask) throw C4AulExecError(FormatString("MatTexMask expected string as %dth element in array.", (int)i).getData()); UnmaskSpec(smask); } } else { // Init by string C4String *smask = spec.getStr(); if (smask) UnmaskSpec(smask); else { if (spec) throw C4AulExecError("MatTexMask expected string or array of strings."); // nil defaults to everything except index zero unmasked sky_mask = std::vector<bool>(256, true); tunnel_mask = std::vector<bool>(256, true); sky_mask[0] = false; tunnel_mask[0] = false; } } }
void C4Effect::SetPropertyByS(C4String * k, const C4Value & to) { if (k >= &Strings.P[0] && k < &Strings.P[P_LAST]) { switch(k - &Strings.P[0]) { case P_Name: if (!to.getStr() || !*to.getStr()->GetCStr()) throw C4AulExecError("effect: Name has to be a nonempty string"); C4PropListNumbered::SetPropertyByS(k, to); ReAssignCallbackFunctions(); return; case P_Priority: throw C4AulExecError("effect: Priority is readonly"); case P_Interval: iInterval = to.getInt(); return; case P_CommandTarget: throw C4AulExecError("effect: CommandTarget is readonly"); case P_Target: throw C4AulExecError("effect: Target is readonly"); case P_Time: iTime = to.getInt(); return; case P_Prototype: throw new C4AulExecError("effect: Prototype is readonly"); } } C4PropListNumbered::SetPropertyByS(k, to); }
static bool FnLayerSetPixel(C4PropList * _this, int32_t x, int32_t y, const C4Value& fg_value_c4v, const C4Value& bg_value_c4v) { // Layer script function: Set pixel at position x,y to to_value in _this layer C4MapScriptLayer *layer = _this->GetMapScriptLayer(); if (!layer) return false; uint8_t fg, bg; if (fg_value_c4v.GetType() == C4V_Nil) { fg = layer->GetPix(x,y,0); } else { const C4Value& val = fg_value_c4v; C4String *str = val.getStr(); if (str != NULL) { if (!TexColSingle(str->GetCStr(), fg)) throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value."); } else { if (!Inside(val.getInt(), 0, 255)) throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value."); fg = val.getInt(); } } if (bg_value_c4v.GetType() == C4V_Nil) { bg = layer->GetBackPix(x,y,0); } else { const C4Value& val = bg_value_c4v; C4String *str = val.getStr(); if (str != NULL) { if (!TexColSingle(str->GetCStr(), bg)) throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value."); } else { if (!Inside(val.getInt(), 0, 255)) throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value."); bg = val.getInt(); } } return layer->SetPix(x,y,fg,bg); }
void C4ValueArray::SetItem(int32_t iElem, const C4Value &Value) { // enlarge if (iElem < -iSize) throw C4AulExecError("array access: index out of range"); else if (iElem < 0) iElem = iSize + iElem; else if (iElem >= iSize && iElem < MaxSize) this->SetSize(iElem + 1); // out-of-memory? This might not get caught, but it's better than a segfault if (iElem >= iSize) throw C4AulExecError("array access: index too large"); // set pData[iElem]=Value; }
C4MapScriptAlgo *FnParAlgo(C4PropList *algo_par) { // Convert script function parameter to internal C4MapScriptAlgo class. Also resolve all parameters and nested child algos. if (!algo_par) return NULL; // if algo is a layer, take that directly C4MapScriptLayer *algo_layer = algo_par->GetMapScriptLayer(); if (algo_layer) return new C4MapScriptAlgoLayer(algo_layer); // otherwise, determine by proplist parameter "algo" switch (algo_par->GetPropertyInt(P_Algo)) { case MAPALGO_Layer: return new C4MapScriptAlgoLayer(algo_par); case MAPALGO_RndChecker: return new C4MapScriptAlgoRndChecker(algo_par); case MAPALGO_And: return new C4MapScriptAlgoAnd(algo_par); case MAPALGO_Or: return new C4MapScriptAlgoOr(algo_par); case MAPALGO_Xor: return new C4MapScriptAlgoXor(algo_par); case MAPALGO_Not: return new C4MapScriptAlgoNot(algo_par); case MAPALGO_Offset: return new C4MapScriptAlgoOffset(algo_par); case MAPALGO_Scale: return new C4MapScriptAlgoScale(algo_par); case MAPALGO_Rotate: return new C4MapScriptAlgoRotate(algo_par); case MAPALGO_Rect: return new C4MapScriptAlgoRect(algo_par); case MAPALGO_Ellipsis: return new C4MapScriptAlgoEllipsis(algo_par); case MAPALGO_Polygon: return new C4MapScriptAlgoPolygon(algo_par); case MAPALGO_Lines: return new C4MapScriptAlgoLines(algo_par); case MAPALGO_Turbulence: return new C4MapScriptAlgoTurbulence(algo_par); case MAPALGO_Border: return new C4MapScriptAlgoBorder(algo_par); case MAPALGO_Filter: return new C4MapScriptAlgoFilter(algo_par); default: throw C4AulExecError(FormatString("got invalid algo: %d", algo_par->GetPropertyInt(P_Algo)).getData()); } return NULL; }
C4MapScriptAlgoLayer::C4MapScriptAlgoLayer(const C4PropList *props) { // Get MAPALGO_Layer properties C4PropList *layer_pl = props->GetPropertyPropList(P_Layer); if (!layer_pl || !(layer = layer_pl->GetMapScriptLayer())) throw C4AulExecError("C4MapScriptAlgoLayer: Expected layer in \"Layer\" property."); }
C4ValueArray * C4ValueArray::GetSlice(int32_t startIndex, int32_t endIndex) { // adjust indices so that the default end index works and that negative numbers count backwards from the end of the string if (startIndex > iSize) startIndex = iSize; else if (startIndex < -iSize) throw C4AulExecError("array slice: start index out of range"); else if (startIndex < 0) startIndex += iSize; if (endIndex > iSize) endIndex = iSize; // this also processes the MAX_INT default if no parameter is given in script else if (endIndex < -iSize) throw C4AulExecError("array slice: end index out of range"); else if (endIndex < 0) endIndex += iSize; C4ValueArray* NewArray = new C4ValueArray(std::max(0, endIndex - startIndex)); for (int i = startIndex; i < endIndex; ++i) NewArray->pData[i - startIndex] = pData[i]; return NewArray; }
bool C4MapScriptAlgo::GetXYProps(const C4PropList *props, C4PropertyName k, int32_t *out_xy, bool zero_defaults) { // Evaluate property named "k" in proplist props to store two numbers in out_xy: // If props->k is a single integer, fill both numbers in out_xy with it // If props->k is an array, check that it contains two numbers and store them in out_xy if (!props->HasProperty(&Strings.P[k])) { if (zero_defaults) out_xy[0] = out_xy[1] = 0; return false; } C4Value val; C4ValueArray *arr; props->GetProperty(k, &val); if ((arr = val.getArray())) { if (arr->GetSize() != 2) throw C4AulExecError(FormatString("C4MapScriptAlgo: Expected either integer or array with two integer elements in property \"%s\".", Strings.P[k].GetCStr()).getData()); out_xy[0] = arr->GetItem(0).getInt(); out_xy[1] = arr->GetItem(1).getInt(); } else { out_xy[0] = out_xy[1] = val.getInt(); } return true; }
C4MapScriptAlgoFilter::C4MapScriptAlgoFilter(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) { // Get MAPALGO_Filter properties C4Value spec; if (!props->GetProperty(P_Filter, &spec)) throw C4AulExecError("MapScriptAlgoFilter without Filter property."); filter.Init(spec); }
C4MapScriptAlgoPolygon::C4MapScriptAlgoPolygon(const C4PropList *props) { // Get MAPALGO_Polygon properties C4Value vptx, vpty; props->GetProperty(P_X, &vptx); props->GetProperty(P_Y, &vpty); C4ValueArray *ptx = vptx.getArray(), *pty = vpty.getArray(); if (!ptx || !pty || ptx->GetSize() != pty->GetSize()) throw C4AulExecError("C4MapScriptAlgoPolygon: Expected two equally sized int arrays in properties \"X\" and \"Y\"."); poly.resize(ptx->GetSize()); for (int32_t i=0; i<ptx->GetSize(); ++i) { poly[i].x = ptx->GetItem(i).getInt(); poly[i].y = pty->GetItem(i).getInt(); } wdt = props->GetPropertyInt(P_Wdt); if (!wdt) wdt = 1; empty = !!props->GetPropertyInt(P_Empty); open = !!props->GetPropertyInt(P_Open); if (open && !empty) throw C4AulExecError("C4MapScriptAlgoPolygon: Only empty polygons may be open."); }
C4MapScriptAlgoModifier::C4MapScriptAlgoModifier(const C4PropList *props, int32_t min_ops, int32_t max_ops) { // Evaluate "Op" property of all algos that take another algo or layer as an operand // Op may be a proplist or an array of proplists C4Value vops; int32_t n; C4ValueArray temp_ops; props->GetProperty(P_Op, &vops); C4ValueArray *ops = vops.getArray(); if (!ops) { C4PropList *op = vops.getPropList(); if (op) { temp_ops.SetItem(0, vops); ops = &temp_ops; n = 1; } } else { n = ops->GetSize(); } if (!ops || n<min_ops || (max_ops && n>max_ops)) throw C4AulExecError(FormatString("C4MapScriptAlgo: Expected between %d and %d operands in property \"Op\".", (int)min_ops, (int)max_ops).getData()); operands.resize(n); try { // can easily crash this by building a recursive prop list // unfortunately, protecting against that is not trivial for (int32_t i=0; i<n; ++i) { C4MapScriptAlgo *new_algo = FnParAlgo(ops->GetItem(i).getPropList()); if (!new_algo) throw C4AulExecError(FormatString("C4MapScriptAlgo: Operand %d in property \"Op\" not valid.", (int)i).getData()); operands[i] = new_algo; } } catch (...) { Clear(); throw; } }
void C4Effect::ResetProperty(C4String * k) { if (k >= &Strings.P[0] && k < &Strings.P[P_LAST]) { switch(k - &Strings.P[0]) { case P_Name: throw C4AulExecError("effect: Name has to be a nonempty string"); case P_Priority: throw C4AulExecError("effect: Priority is readonly"); case P_Interval: iInterval = 0; return; case P_CommandTarget: throw C4AulExecError("effect: CommandTarget is readonly"); case P_Target: throw C4AulExecError("effect: Target is readonly"); case P_Time: iTime = 0; return; case P_Prototype: throw new C4AulExecError("effect: Prototype is readonly"); } } C4PropListNumbered::ResetProperty(k); }
static C4PropList *FnCreateLayer(C4PropList * _this, C4String *mattex_fill, int32_t width, int32_t height) { // Layer script function: Create new layer filled by mattex_fill of size width,height as sub layer of _this map // Size defaults to _this layer size uint8_t fg = 0, bg = 0; if (mattex_fill && mattex_fill->GetCStr()) if (!FnParTexCol(mattex_fill, fg, bg)) throw C4AulExecError(FormatString("CreateLayer: Invalid fill material.").getData()); C4MapScriptLayer *layer = _this->GetMapScriptLayer(); if (!layer) return NULL; if (!width && !height) { width = layer->GetWdt(); height = layer->GetHgt(); } if (width<=0 || height<=0) throw C4AulExecError(FormatString("CreateLayer: Invalid size (%d*%d).", (int)width, (int)height).getData()); C4MapScriptMap *map = layer->GetMap(); if (!map) return NULL; layer = map->CreateLayer(width, height); if (fg != 0 || bg != 0) layer->Fill(fg, bg, layer->GetBounds(), NULL); return layer; }
C4MapScriptAlgoLines::C4MapScriptAlgoLines(const C4PropList *props) { // Get MAPALGO_Lines properties lx = props->GetPropertyInt(P_X); ly = props->GetPropertyInt(P_Y); if (!lx && !ly) throw C4AulExecError("C4MapScriptAlgoLines: Invalid direction vector. Either \"X\" or \"Y\" must be nonzero!"); ox = props->GetPropertyInt(P_OffX); oy = props->GetPropertyInt(P_OffY); // use sync-safe distance function to calculate line width int32_t l = Distance(0,0,lx,ly); // default distance: double line width, so lines and gaps have same width distance = props->GetPropertyInt(P_Distance); if (!distance) distance = l+l; // 1+1=2 // cache for calculation ll = int64_t(lx)*lx+int64_t(ly)*ly; dl = int64_t(distance) * l; }
void C4PropList::SetPropertyByS(C4String * k, const C4Value & to) { assert(!constant); if (k == &Strings.P[P_Prototype]) { C4PropList * newpt = to.getPropList(); for(C4PropList * it = newpt; it; it = it->GetPrototype()) if(it == this) throw C4AulExecError("Trying to create cyclic prototype structure"); prototype.SetPropList(newpt); } else if (Properties.Has(k)) { Properties.Get(k).Value = to; } else { Properties.Add(C4Property(k, to)); } }
void C4ValueArray::SetSlice(int32_t startIndex, int32_t endIndex, const C4Value &Val) { // maximum bounds if(startIndex >= MaxSize) throw C4AulExecError("array slice: start index exceeds maximum capacity"); // index from back if(startIndex < 0) startIndex += iSize; if(endIndex < 0) endIndex += iSize; // ensure relevant bounds if(startIndex < 0) throw C4AulExecError("array slice: start index out of range"); if(endIndex < 0) throw C4AulExecError("array slice: end index out of range"); if(endIndex < startIndex) endIndex = startIndex; // setting an array? if(Val.GetType() == C4V_Array) { const C4ValueArray &Other = *Val._getArray(); // Remember that &Other could be equal to this, carefull with modifying pData // Calculcate new size int32_t iNewEnd = std::min(startIndex + Other.GetSize(), (int32_t)MaxSize); int32_t iNewSize = iNewEnd; if(endIndex < iSize) iNewSize += iSize - endIndex; iNewSize = std::min(iNewSize, (int32_t)MaxSize); int32_t iOtherSize = Other.GetSize(); if(iNewSize != iSize) { int32_t i,j; C4Value* pnData = pData; if(iNewSize > iCapacity) { pnData = new C4Value [iNewSize]; // Copy first part of old array for(i = 0; i < startIndex && i < iSize; ++i) pnData[i] = pData[i]; } // Copy the second slice of the new array for(i = iNewEnd, j = endIndex; i < iNewSize; ++i, ++j) { assert(j < iSize); pnData[i] = pData[j]; } // Copy the data // Since pnData and pData can be the same, we can not copy with //for(i = startIndex, j = 0; i < iNewEnd; ++i, ++j) // but have to start from the end of the copied sequence. That works, since j <= i for(i = iNewEnd - 1, j = iNewEnd - startIndex - 1; i >= startIndex; --i, --j) { assert(j < iOtherSize); pnData[i] = Other.pData[j]; } // Other values should have been initialized to 0 by new if(pData != pnData) { delete [] pData; pData = pnData; iCapacity = iSize = iNewSize; } else { // "ignore" the now unused part for(i = iNewSize; i < iSize; ++i) pData[i].Set0(); iSize = iNewSize; } } else // slice has the same size as inserted array // Copy the data. changing pData does not break because if &Other == this and iNewSize == iSize, nothing happens at all for(int32_t i = startIndex, j = 0; j < iOtherSize; i++, j++) pData[i] = Other.pData[j]; } else { /* if(Val.GetType() != C4V_Array) */ if(endIndex > MaxSize) endIndex = iSize; // Need resize? if(endIndex > iSize) SetSize(endIndex); // Fill for(int32_t i = startIndex; i < endIndex; i++) pData[i] = Val; } }
void C4ParticleValueProvider::Set(const C4ValueArray &fromArray) { startValue = endValue = 1.0f; valueFunction = &C4ParticleValueProvider::Const; size_t arraySize = (size_t) fromArray.GetSize(); if (arraySize < 2) return; int type = fromArray[0].getInt(); switch (type) { case C4PV_Const: if (arraySize >= 2) { SetType(C4PV_Const); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue); } break; case C4PV_Linear: if (arraySize >= 3) { SetType(C4PV_Linear); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::endValue); } break; case C4PV_Random: if (arraySize >= 3) { SetType(C4PV_Random); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::endValue); if (arraySize >= 4) SetParameterValue(VAL_TYPE_INT, fromArray[3], 0, &C4ParticleValueProvider::rerollInterval); alreadyRolled = 0; } break; case C4PV_Direction: if (arraySize >= 2) { SetType(C4PV_Direction); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue); } break; case C4PV_Step: if (arraySize >= 4) { SetType(C4PV_Step); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::currentValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[3], &C4ParticleValueProvider::delay); SetParameterValue(VAL_TYPE_FLOAT, fromArray[4], &C4ParticleValueProvider::maxValue); if (delay == 0.f) delay = 1.f; } break; case C4PV_KeyFrames: if (arraySize >= 5) { SetType(C4PV_KeyFrames); SetParameterValue(VAL_TYPE_INT, fromArray[1], 0, &C4ParticleValueProvider::smoothing); keyFrames.resize(arraySize + 4 - 1); // 2*2 additional information floats at the beginning and ending, offset the first array item, though keyFrameCount = 0; const size_t startingOffset = 2; size_t i = startingOffset; for (; i < arraySize; ++i) { SetParameterValue(VAL_TYPE_KEYFRAMES, fromArray[(int32_t)i], 0, 0, 2 + i - startingOffset); } keyFrameCount = (i - startingOffset) / 2 + 2; startValue = keyFrames[2 + 1]; endValue = keyFrames[2 * keyFrameCount - 1]; // add two points for easier interpolation at beginning and ending keyFrames[0] = -500.f; keyFrames[1] = keyFrames[2 + 1]; keyFrames[2 * keyFrameCount - 2] = 1500.f; keyFrames[2 * keyFrameCount - 1] = keyFrames[keyFrameCount - 1 - 2]; } break; case C4PV_Sin: if (arraySize >= 3) { SetType(C4PV_Sin); // Sin(parameterValue) * maxValue + startValue SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::parameterValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::maxValue); SetParameterValue(VAL_TYPE_FLOAT, fromArray[3], &C4ParticleValueProvider::startValue); } break; case C4PV_Speed: if (arraySize >= 3) { SetType(C4PV_Speed); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue); } break; case C4PV_Wind: if (arraySize >= 3) { SetType(C4PV_Wind); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue); } break; case C4PV_Gravity: if (arraySize >= 3) { SetType(C4PV_Gravity); SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor); SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue); } break; default: throw C4AulExecError("invalid particle value provider supplied"); break; } }