From 57e63a5275385499d81b42806e7b592ee55cf064 Mon Sep 17 00:00:00 2001 From: Sven Eberhardt Date: Tue, 19 Mar 2013 00:35:00 +0100 Subject: [PATCH] Added support for scripted maps (Map.c) and documentation. --- CMakeLists.txt | 3 + docs/sdk/scenario/index.xml | 4 + docs/sdk/script/MapScript.xml | 474 ++++++++++++++ .../Libraries.ocd/Map.ocd/DefCore.txt | 4 + .../Libraries.ocd/Map.ocd/Graphics.png | Bin 0 -> 154 bytes .../Libraries.ocd/Map.ocd/Script.c | 125 ++++ src/c4group/C4Components.h | 3 +- src/game/C4Game.cpp | 5 + src/gamescript/C4Script.h | 1 + src/graphics/CSurface8.h | 5 + src/landscape/C4Landscape.cpp | 8 +- src/landscape/C4MapCreatorS2.cpp | 7 +- src/landscape/C4MapScript.cpp | 586 ++++++++++++++++++ src/landscape/C4MapScript.h | 329 ++++++++++ src/landscape/C4MapScriptAlgo.cpp | 482 ++++++++++++++ src/lib/C4Rect.h | 6 +- src/script/C4AulFunc.cpp | 5 + src/script/C4AulParse.cpp | 25 +- src/script/C4PropList.cpp | 14 + src/script/C4PropList.h | 5 +- src/script/C4ScriptHost.h | 1 + src/script/C4ScriptStandalone.cpp | 9 + src/script/C4StringTable.cpp | 17 + src/script/C4StringTable.h | 17 + 24 files changed, 2115 insertions(+), 20 deletions(-) create mode 100644 docs/sdk/script/MapScript.xml create mode 100644 planet/Objects.ocd/Libraries.ocd/Map.ocd/DefCore.txt create mode 100644 planet/Objects.ocd/Libraries.ocd/Map.ocd/Graphics.png create mode 100644 planet/Objects.ocd/Libraries.ocd/Map.ocd/Script.c create mode 100644 src/landscape/C4MapScript.cpp create mode 100644 src/landscape/C4MapScript.h create mode 100644 src/landscape/C4MapScriptAlgo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5eab68d5e..ff15beeb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,9 @@ set(OC_CLONK_SOURCES src/landscape/C4MapCreatorS2.cpp src/landscape/C4MapCreatorS2.h src/landscape/C4Map.h + src/landscape/C4MapScript.cpp + src/landscape/C4MapScriptAlgo.cpp + src/landscape/C4MapScript.h src/landscape/C4MassMover.cpp src/landscape/C4MassMover.h src/landscape/C4Material.cpp diff --git a/docs/sdk/scenario/index.xml b/docs/sdk/scenario/index.xml index bc516778d..c9fd855bc 100644 --- a/docs/sdk/scenario/index.xml +++ b/docs/sdk/scenario/index.xml @@ -25,6 +25,10 @@
Landscape.txt
Advanced scenario designers can use this component to define highly complex, fully random generated dynamic landscapes. This does require certain mathematical skill and some patience, however. +
+
Map.c
+
+ Script for dynamic generation of map. See map script documentation.
diff --git a/docs/sdk/script/MapScript.xml b/docs/sdk/script/MapScript.xml new file mode 100644 index 000000000..7e53aa25b --- /dev/null +++ b/docs/sdk/script/MapScript.xml @@ -0,0 +1,474 @@ + + + + + Map script + Map script + + Map scripts provide a powerful method to generate diverse, dynamic maps with just a few lines of simple script code. Map scripts can be used to generate new maps, as well as modify existing maps defined as static Map.bmp or dynamic Landscape.txt. + Introduction + A map script is simply a script file called Map.c placed in a scenario. On scenario initialization, the engine calls the local function called InitializeMap in this script. If the function returns true, the map will be used by the engine. If false is returned, the map is discarded and the regular fallback map is created. + Here an example of a simple map script: + /* A simple map */ + +#include Library_Map + +func InitializeMap(proplist map) +{ + // Create a big map + Resize(150,150); + // Draw earth + DrawRegularGround(); + // Draw some resources into the ground + DrawWaterVeins(3, [0,map.Hgt/3,map.Wdt,map.Hgt*2/3]); + DrawCoal(6); + DrawSulphur(4); + DrawRock(15); + DrawOre(4); + DrawGold(2*GetStartupPlayerCount()); // amount of gold depends on player count! + // Make sure liquids don't border tunnel or sky sideways + FixLiquidBorders(); + // Done. Use this map. + return true; +} + This draws a pretty boring standard map with basic resources. It makes use of some high-level helper functions such as DrawRegularGround or DrawCoal. which are included in the definition named Library_Map in Objects.ocd/Libraries.ocd. All map scripts should include this definition. + + Layers + + All map draw functions work on layers, which are simply 8 bit image surfaces. The map itself is a layer, but additional layers can be created as temporary buffers using the CreateLayer or Duplicate script functions. Additional layers are bound to the map they were created from and destroyed alongside with it when map drawing is complete. + In C4Script, map layers are represented as prop lists. They have the implicit properties Wdt and Hgt, which contain the width and height of the surface respectively. To resize a map or layer, use the Resize() function. Do not modify Wdt or Hgt directly. + For example, the following code: + var layer = CreateLayer("Earth"); +layer->DrawCoal(); +Blit(layer, [0,0,layer.Wdt/2,layer.Hgt]); + would create a secondary layer filled with earth. It would then draw coal onto the layer and finally copy only the left half of its contents to the main map. + + + Algorithms + + Algorithms are the core concept of dynamic map creation to point drawing operations to specific subregions of the map only. An algorithm is a function that maps a position (int x, int y) to either a pixel color (int) or a mask value (bool). + Algorithms are defined as prop list with the Algo property set to one of the MAPALGO_* constants and additional algorithm parameters set as properties. They can then be passed to one of the drawing functions (Draw or Blit), which will evaluate the algorithm at all positions and draw pixel values accordingly. + For example, the following code would draw rectangles of earth in a checkerboard pattern: + Draw("Earth", {Algo=MAPALGO_RndChecker, Wdt=5, Hgt=5}); + In addition to pattern-generating algorithms, there are also modifier algorithms that take other algorithms as parameters. For example, the Turbulence algorithm jumbles all pixels of the underlying algorithm around to create a noisy pattern: + var checkerboard = {Algo=MAPALGO_RndChecker, Wdt=10, Hgt=10}; +var jumbled_checkerboard = {Algo=MAPALGO_Turbulence, Amplitude=10, Scale=10}; +Draw("Earth", jumbled_checkerboard); + Modifier algorithms can also be applied to layer contents directly. For example, to flip the contents of the current map, one could write: + // Backup contents of current map +var copy_layer = Duplicate(); +// Redraw flipped horizontally +Blit({Algo=MAPALGO_Scale, OffX=Wdt/2, X=-100, Op=copy_layer}); + + Note: If you are using the target layer in a drawing command, always draw from a copy. Otherwise, the result is undefined. + SCRIPTALGO_Layer + Returns the pixel value at the x,y position of the given layer. Instead of passing a SCRIPTALGO_Layer prop list, layers can also be passed directly as algorithms. + ParameterDefaultMeaning + + Layer + + The layer from which pixel values are taken. + +
+ SCRIPTALGO_RndChecker + Returns values from a checkerboard pattern of rectangles that are filled with ones or zeros. + ParameterDefaultMeaning + + Seed + Random(65536) + If nonzero, the checkerboard pattern is generated from a fixed seed. + + Ratio + 50 + Percentage of checkerboard fields that are one. + + Wdt + 10 + Width of rectangles. + + Hgt + 10 + Height of rectangles + + FixedOffset + false + If true, the pattern always starts at position (0,0). Otherwise, it is offset by a random phase. + +
+ SCRIPTALGO_Rect + Returns one if the position is in a given rectangle and zero otherwise. + ParameterDefaultMeaning + + X + 0 + Left side of rectangle (pixel is included). + + Y + 0 + Top side of rectangle (pixel is included). + + Wdt + 0 + Width of rectangle. + + Hgt + 0 + Height of rectangle. + +
+ SCRIPTALGO_Ellipsis + Returns one if the position is in a given ellipsis and zero otherwise. + ParameterDefaultMeaning + + X + 0 + Horizontal center of ellipsis. + + Y + 0 + Vertical center of ellipsis. + + Wdt + 10 + Horizontal radius of ellipsis. + + Hgt + 10 + Vertical radius of ellipsis + +
+ SCRIPTALGO_Polygon + Returns one if the position is in a given polygon or on its border and zero otherwise. + ParameterDefaultMeaning + + X + + Array of x coordinates of polygon points. + + Y + + Array of y coordinates of polygon points. + + Wdt + 1 + Width of border lines of polygon. + + Empty + false + If true, the polygon is not filled and only the border is drawn. + + Open + false + If true, the last segment of the polygon is not drawn. Useful to draw lines. Only valid if Empty is true. + +
+ SCRIPTALGO_And + Returns zero if any of the operands is zero. Otherwise, returns the value of the last operand. If there are zero operands, always returns zero. + ParameterDefaultMeaning + + Op + + Array of algorithms that are tested. + +
+ SCRIPTALGO_Or + Returns the first operand that is nonzero. If all operands are zero, returns zero. If there are zero operands, always returns zero. + ParameterDefaultMeaning + + Op + + Array of algorithms that are tested. + +
+ SCRIPTALGO_Not + Returns one if the operand is zero. Returns zero otherwise. + ParameterDefaultMeaning + + Op + + Algorithms that is negated. + +
+ SCRIPTALGO_Xor + If exactly one of the two operands is nonzero, returns that operand. Otherwise, returns zero. + ParameterDefaultMeaning + + Op + + Array of two algorithms that are tested. + +
+ SCRIPTALGO_Offset + Moves its operand by an offset. + ParameterDefaultMeaning + + Op + + Algorithms that is being manipulated. + + OffX + 0 + Horizontal offset to the right. + + OffY + + Vertical offset downwards. + +
+ SCRIPTALGO_Scale + Scales its operand by a point. + ParameterDefaultMeaning + + Op + + Algorithms that is being manipulated. + + X + 100 + Horizontal scaling in percent. Values smaller than zero flip the operand horizontally. + + Y + 100 + Vertical scaling in percent. Values smaller than zero flip the operand vertically. + + OffX + 0 + X position of fixed point that remains in position. + + OffY + 0 + Y position of fixed point that remains in position. + +
+ SCRIPTALGO_Rotate + Rotates its operand around a point. + ParameterDefaultMeaning + + Op + + Algorithms that is being manipulated. + + R + 0 + Rotation angle in degrees (0 to 360). Positive values rotate counter-clockwise. + + OffX + 0 + X position of fixed point that remains in position. + + OffY + 0 + Y position of fixed point that remains in position. + +
+ SCRIPTALGO_Turbulence + Jumbles its operand around by moving points by a randomized offset. + ParameterDefaultMeaning + + Seed + Random(65536) + If nonzero, the offset map is generated from a fixed seed. + + Amplitude + 10 + Maximum range by which pixels may be moved in a single step. Movement in any direction is half of the amplitude. Can be an single integer for equal movement in both dimensions or an array of two integers for separate amplitudes for horizontal and vertical movement. + + Scale + 10 + Distance of points for which the amplitude is randomized. A large scale relative to the amplitude creates more broadly scaled, regular turbulence, while a small scale can cause borders to look more jumpey. Can be an single integer for equal scale in both dimensions or an array of two integers for separate scales horizontally and vertically. + + Iterations + 2 + Number of times each point is pushed around. The amplitude of the n'th successive push is reduced by 1/n. + +
+ SCRIPTALGO_Border + Returns true for positions that lie on an inner or outer border of an operand. An inner border is defined as a position where the operand is nonzero and a position where it is zero lies within inner border width range. An outer border is defined as a position where the operand is zero and a position where it is nonzero lies within outer border width range. Note that borders are only searched in four directions (left, right, upwards, downwards) and not diagonally. This means that for a square, outer borders to not catch the corners. + ParameterDefaultMeaning + + Op + + Algorithm of which the border is to be determined. + + Wdt + 1 + Border width in all directions. Positive integer for inner border; negative integer for outer border. Can also be an array of two integers of opposing signs for inner and outer borders. + + Left + + Border width to the left side. Definition like Wdt. Falls back to Wdt if not specified. + + Top + + Border width upwards. Definition like Wdt. Falls back to Wdt if not specified. + + Right + + Border width to the right side. Definition like Wdt. Falls back to Wdt if not specified. + + Bottom + + Border width downwards. Definition like Wdt. Falls back to Wdt if not specified. + +
+ SCRIPTALGO_Filter + Return only pixel values of the operand that match the mask specification. Returns zero for other pixels. + ParameterDefaultMeaning + + Op + + Operand algorithm that is being filtered. + + Filter + + Mask specification (see section "Material-texture masks" below) + +
+
+ + Script function parameters + + Map drawing functions follow a common syntax for passing certain structures: + Rectangles (array rect) + All rectangles are given in the format [left, top, width, height], where the left and top pixel rows are included and left+width and top+height pixel rows are excluded. Unless otherwise specified, rect can always be nil, in which case the area defaults to the whole map or layer ([0,0,this.Wdt,this.Hgt]). + Material-texture definitions (string mattex) + When a material is specified for the drawing functions, the following definitions are valid: + + + + String + Example + Meaning + + + Material + Earth + Draws the given material in its default texture as underground (tunnel background) material. + + + Material-Texture + Earth-earth_topSoil + Draws the given material with the given texture as underground material. + + + ^Material + ^Water + Draws the given material with its default texture as overground (sky background) material. + + + ^Material-Texture + ^Earth-earth_rough + Draws the given material with the given texture as overground material. + + + Sky + Sky + Draws a sky material. Within the map generator, explicit sky is drawn as IFT (0x80), which is converted to index zero on map drawing. That way, sky can be blitted to other layers without being transparent. + + + Transparent + Transparent + Draws with index 0. + +
+
+ Material-texture masks (string mask_spec) + When a material is specified as a masking function, the following definitions are valid: + + + + String + Example + Meaning + + + Material + Earth + True for given material with any texture and any (sky or tunnel) background. + + + Material-Texture + Earth-earth_topSoil + True for the given material with the given texture and any (sky or tunnel) background. + + + Sky + Sky + True for explicit sky material (0x80) only. Not true for transaprent (0) pixels. + + + Transparent + Transparent + True for transparent pixels (index 0) only. + + + Background + Background + True for all background materials (e.g. Tunnel, BrickBack and Sky). + + + Liquid + Liquid + True for all liquids (e.g. Water, Acid, Lava and DuroLava). + + + Solid + Solid + True for solid materials (e.g. Earth, Rock, Brick, etc.). + + + * + * + True for all materials. + + + ^Definition + ^Rock-rock_cracked + True for the definition if overground (sky background) only. + + + &Definition + &Liquid + True for the definition if underground (tunnel background) only. The example would match all underground liquids. + + + ~Definition + ~^* + Inverts the definition, i.e. true only if the definition would originally be false. The example would match all underground materials. + +
+
+
+ + Script functions + + All drawing functions are defined in the MapLayer static prop list. Because the Map.c script file is also evaluated in this context with the current map as this pointer, all drawing functions can be called directly by name in that script (e.g.: Resize(150,150)). In other script contexts or if the function is to be executed on a layer instead of on the main map, the base object must be given explicitely (e.g.: map->Resize(150,150), where map is the parameter passed to InitializeMap). + Because layers derive from the MapLayer prop list, all script functions defined in the Map.c and included script files are also available on any layer. + Internal engine functions + + bool Draw(string mattex, proplist mask_algo, array rect); + Draws the material given by mattex on all pixels within rect if the algorithm given by mask_algo returns a value other than zero. Returns true on success. + bool Blit(proplist mask_algo, array rect); + Same as draw, but draws the result of evaluation of mask_algo directly instead of a material given by mattex. Because mask_algo can also be a layer, this function can be used to copy layer contents onto other layers or the map. If mask_algo evaluates to zero, nothing is drawn and the original pixel value is kept. + proplist CreateLayer(string mattex_fill, int width, int height); + Creates a new layer of size width,height. If no size is given, the layer is created in the same size as the calling context layer or map. The new layer is filled with the pixel color given by mattex_fill, or with zeroes if mattex_fill is nil. Returns the newly created layer. + bool Resize(int new_width, int new_height); + Recreates the calling layer or map surface in the given size. All contents are deleted and the layer is filled with zeroes. Use functions Duplicate and Blit to backup and restore any old layer contents if you want to extent the map without losing its contents. Returns true on success. + proplist Duplicate(any mask_spec, array rect); + Creates a new layer with the same size and surface contents as this layer. If a rect is given, the new layer is smaller and contains only the portion included in rect. If mask_spec is given, only pixels passing the mask are set and all other pixels in the new layer are zero. + int GetPixel(int x, int y); + Gets the pixel color at the given position in this layer. If x,y is outside the layer, zero is returned. + bool SetPixel(int x, int y, int new_color); + Sets the pixel at position x,y in this layer to new_color. Returns true on success. + int GetPixelCount(any mask_spec, array rect); + Returns number of pixels on this layer or map within rect that fulfill mask_spec. + bool FindPosition(proplist out_pos, mask_spec, array rect, int max_tries); + Tries to find a position on this layer for which the pixel color matches mask_spec. If a position is found, true is returned and the position is set as X and Y parameters in the out_pos prop list. If no position is found after max_tries, the function will walk through all pixels of the layer starting from a random starting position to find a point. If still no position is found, false is returned and out_pos is not changed. max_tries defaults to 500. + + + + +
+ Sven22013-03 +
diff --git a/planet/Objects.ocd/Libraries.ocd/Map.ocd/DefCore.txt b/planet/Objects.ocd/Libraries.ocd/Map.ocd/DefCore.txt new file mode 100644 index 000000000..41c30e340 --- /dev/null +++ b/planet/Objects.ocd/Libraries.ocd/Map.ocd/DefCore.txt @@ -0,0 +1,4 @@ +[DefCore] +id=Library_Map +Version=5,2,0,1 +Category=C4D_StaticBack diff --git a/planet/Objects.ocd/Libraries.ocd/Map.ocd/Graphics.png b/planet/Objects.ocd/Libraries.ocd/Map.ocd/Graphics.png new file mode 100644 index 0000000000000000000000000000000000000000..ff1d0ae6c86a51bc4c51906a1bdb58bb38ce6a90 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|%|szQ_YPoCO|{#S9EFZXnDkGR4^uD9DoT z=nJGb_SO6joDAeEBzpw;GB8xBGB7kWGcf%852Rl*Fq9fFFuY1&V6d9Oz#yJKDgG$X r5C#EH7srr_TS-6u|F>s$XkcXE_`~$?_99;~pd5pztDnm{r-UW|qz)#t literal 0 HcmV?d00001 diff --git a/planet/Objects.ocd/Libraries.ocd/Map.ocd/Script.c b/planet/Objects.ocd/Libraries.ocd/Map.ocd/Script.c new file mode 100644 index 000000000..60de8bf5e --- /dev/null +++ b/planet/Objects.ocd/Libraries.ocd/Map.ocd/Script.c @@ -0,0 +1,125 @@ +/*-- + Library_Map + Authors: Sven2 + + Utility functions for map drawing in InitializeMap(). +--*/ + +// Returns p if p is an int. If p is an array of two ints, returns a +// random value between both (assuming p[1]>p[0]) +func EvalIntPar(p) { if (GetType(p) == C4V_Array) return p[0]+Random(p[1]-p[0]); return p; } + +// Returns p if p is an int. If p is an array of two ints, returns the +// medium value of both +func EvalIntParM(p) { if (GetType(p) == C4V_Array) return (p[0]+p[1])/2; return p; } + +func DrawVaried(string mat, proplist algo, array rect, vary_mats) +{ + // Draw material and put rndchecker variations of other materials on it + if (GetType(vary_mats) != C4V_Array || (GetLength(vary_mats)==3 && GetType(vary_mats[1])==C4V_Int)) vary_mats = [vary_mats]; + var ratio = 100/(GetLength(vary_mats)+1); + var main_shape = this->CreateLayer(); + main_shape->Draw(mat, algo, rect); + this->Blit(main_shape, rect); + for (var vary_def in vary_mats) + { + var sx=3,sy=3; + if (GetType(vary_def) == C4V_Array) + { + sx=vary_def[1]; sy = vary_def[2]; + vary_def = vary_def[0]; + } + var rand_algo = {Algo=MAPALGO_RndChecker, Ratio=ratio, Wdt=sx, Hgt=sy}; + var turb_algo = {Algo=MAPALGO_Turbulence, Amplitude=12, Scale=8, Op=rand_algo}; + this->Draw(vary_def, {Algo=MAPALGO_And, Op=[main_shape, turb_algo]}); + } + return main_shape; +} + +func DrawSpots(string mat, int num, sizex, sizey, array rect, inmat, vary_mats) +{ + // Default parameters + if (!inmat) inmat="Earth"; + if (!sizex) sizex = [5, 20]; + if (!sizey) sizey = [5, 7]; + if (!num) num=Max(this->GetPixelCount(inmat, rect) / (EvalIntParM(sizex)*EvalIntParM(sizey)*10), 1); + // Draw num spots + var spot = {Algo=MAPALGO_Ellipsis}; + while (num--) + { + if (!this->FindPosition(spot, inmat, rect)) break; + var mask = this->Duplicate(inmat); + spot.Wdt = EvalIntPar(sizex)/2; + spot.Hgt = EvalIntPar(sizey)/2; + var algo = {Algo=MAPALGO_And, Op=[mask, {Algo=MAPALGO_Turbulence, Amplitude=Max(spot.Wdt, spot.Hgt), Scale=Max(spot.Wdt, spot.Hgt), Op=spot}]}; + if (vary_mats) + DrawVaried(mat, algo, rect, vary_mats); + else + this->Draw(mat, algo, rect); + } + return true; +} + +func DrawCoal(int num, array rect) { return DrawSpots("Coal", num, [20,60], [4,8], rect, nil); } +func DrawSulphur(int num, array rect) { return DrawSpots("Sulphur", num, [20,60], [8,12], rect, nil); } +func DrawOre(int num, array rect) { return DrawSpots("Ore", num, [8,12], [14,20], rect, nil); } +func DrawGold(int num, array rect) { return DrawSpots("Gold", num, [10,14], [10,14], rect, nil); } +func DrawRock(int num, array rect) { return DrawSpots("Rock-rock", num, [20,80], [6,8], rect, nil, [["Rock-rock_cracked", 3,10], ["Granite", 6,2]]); } + +func DrawWaterVeins(int num, array rect) +{ + while (num--) DrawLiquidVein("Water", 3, 5, rect); + return true; +} + +func DrawLiquidVein(string mat, int wdt, int spread, array rect, inmat) +{ + if (!rect) rect = [0,0,this.Wdt, this.Hgt]; + if (!inmat) inmat="Earth"; + var mask = this->Duplicate(inmat); + var x1 = rect[0]-rect[2]+Random(rect[2]*3); + var y1 = rect[1]-rect[3]+Random(rect[3]*3); + var x2 = rect[0]+Random(rect[2]); + var y2 = rect[1]+Random(rect[3]); + var water = {Algo=MAPALGO_Polygon, X=[x1,x2], Y=[y1,y2], Wdt=wdt}; + var water_rand = {Algo=MAPALGO_Turbulence, Amplitude=30, Scale=30, Op=water}; + var water_rand2 = {Algo=MAPALGO_Turbulence, Amplitude=spread*2, Scale=2, Op=water_rand}; + this->Draw(mat, {Algo=MAPALGO_And, Op=[mask, water_rand2]}); + return true; +} + +func DrawRegularGround(array rect) +{ + // Draw regular (boring) ground level + if (!rect) rect = [0,0,this.Wdt, this.Hgt]; + var ground_rect = {Algo=MAPALGO_Rect, X=-100, Y=rect[1]+rect[3]/4, Wdt=rect[0]+rect[2]+200, Hgt=rect[3]+100}; + var ground_algo = {Algo=MAPALGO_Turbulence, Amplitude=30, Scale=30, Op=ground_rect}; + var earth_shape = DrawVaried("Earth-earth", ground_algo, rect, [["Earth-earth_dry", 3,10], ["Earth-earth_rough", 6,2]]); + var top1 = {Algo=MAPALGO_Border, Top=[-10, 3], Op=earth_shape}; + var top2 = {Algo=MAPALGO_Border, Top=[-10, 1], Op=earth_shape}; + this->Draw("Earth-earth_midSoil", {Algo=MAPALGO_And, Op=[earth_shape, {Algo=MAPALGO_Turbulence, Amplitude=4, Scale=10, Op=top1}]}); + this->Draw("Earth-earth_topSoil", {Algo=MAPALGO_And, Op=[earth_shape, {Algo=MAPALGO_Turbulence, Amplitude=4, Scale=10, Op=top2}]}); + return true; +} + +func FixLiquidBorders(border_material, lava_border_material) +{ + // Makes sure liquids bordering other liquids as well as liquids + // bordering background materials are surrounded by fix_material + // Default border materials + if (!border_material) border_material = "Earth-earth_topSoil"; + if (!lava_border_material) lava_border_material = "Rock"; + // Find liquid-to-background borders + var liquids = this->Duplicate("Liquid"); + var liquid_borders = {Algo=MAPALGO_Border, Op=liquids, Wdt=-1, Top=0 }; + var background = this->Duplicate("Background"); + background->Draw("Sky", {Algo=MAPALGO_Not, Op=this}); + this->Draw(border_material, {Algo=MAPALGO_And, Op=[liquid_borders, background]}); + // Put lava on top of other liquids + var lava_borders = {Algo=MAPALGO_Border, Op=this->Duplicate(["Lava", "DuroLava"])}; + this->Draw(lava_border_material, {Algo=MAPALGO_And, Op=[lava_borders, liquids]}); + // Put acid on top of water + var acid_borders = {Algo=MAPALGO_Border, Op=this->Duplicate("Acid")}; + this->Draw(border_material, {Algo=MAPALGO_And, Op=[acid_borders, liquids]}); + return true; +} \ No newline at end of file diff --git a/src/c4group/C4Components.h b/src/c4group/C4Components.h index f18275cd1..bad245f4b 100644 --- a/src/c4group/C4Components.h +++ b/src/c4group/C4Components.h @@ -63,6 +63,7 @@ #define C4CFN_DiffLandscape "DiffLandscape.bmp" #define C4CFN_Sky "Sky" #define C4CFN_Script "Script.c|Script%s.c|C4Script%s.c" +#define C4CFN_MapScript "Map.c" #define C4CFN_ScriptStringTbl "StringTbl.txt|StringTbl%s.txt" #define C4CFN_Info "Info.txt" #define C4CFN_Author "Author.txt" @@ -170,7 +171,7 @@ // TODO: proper sorting of scaled def graphics (once we know what order we might load them in...) -#define C4FLS_Scenario "Loader*.bmp|Loader*.png|Loader*.jpeg|Loader*.jpg|Fonts.txt|Scenario.txt|Title*.txt|Info.txt|Desc*.rtf|Icon.png|Icon.bmp|Game.txt|StringTbl*.txt|Teams.txt|Parameters.txt|Info.txt|Sect*.ocg|Music.ocg|*.mid|*.wav|Desc*.rtf|Title.bmp|Title.png|*.ocd|Material.ocg|MatMap.txt|Landscape.bmp|Landscape.png|" C4CFN_DiffLandscape "|Sky.bmp|Sky.png|Sky.jpeg|Sky.jpg|PXS.ocb|MassMover.ocb|CtrlRec.ocb|Strings.txt|Objects.txt|RoundResults.txt|Author.txt|Version.txt|Names.txt|*.ocd|Script.c|Script*.c|System.ocg" +#define C4FLS_Scenario "Loader*.bmp|Loader*.png|Loader*.jpeg|Loader*.jpg|Fonts.txt|Scenario.txt|Title*.txt|Info.txt|Desc*.rtf|Icon.png|Icon.bmp|Game.txt|StringTbl*.txt|Teams.txt|Parameters.txt|Info.txt|Sect*.ocg|Music.ocg|*.mid|*.wav|Desc*.rtf|Title.bmp|Title.png|*.ocd|Material.ocg|MatMap.txt|Landscape.bmp|Landscape.png|" C4CFN_DiffLandscape "|Sky.bmp|Sky.png|Sky.jpeg|Sky.jpg|PXS.ocb|MassMover.ocb|CtrlRec.ocb|Strings.txt|Objects.txt|RoundResults.txt|Author.txt|Version.txt|Names.txt|*.ocd|Script.c|Script*.c|Map.c|System.ocg" #define C4FLS_Section "Scenario.txt|Game.txt|Landscape.bmp|Landscape.png|Sky.bmp|Sky.png|Sky.jpeg|Sky.jpg|PXS.ocb|MassMover.ocb|CtrlRec.ocb|Strings.txt|Objects.txt" #define C4FLS_SectionLandscape "Scenario.txt|Landscape.bmp|Landscape.png|PXS.ocb|MassMover.ocb" #define C4FLS_SectionObjects "Strings.txt|Objects.txt" diff --git a/src/game/C4Game.cpp b/src/game/C4Game.cpp index 57b924d83..0b60b5d2c 100644 --- a/src/game/C4Game.cpp +++ b/src/game/C4Game.cpp @@ -82,6 +82,7 @@ #include #include #include +#include class C4GameSec1Timer : public C4ApplicationSec1Timer { @@ -601,6 +602,7 @@ void C4Game::Clear() MessageInput.Clear(); Info.Clear(); Title.Clear(); + ::MapScript.Clear(); ::GameScript.Clear(); Names.Clear(); GameText.Clear(); @@ -2010,6 +2012,8 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value // Scenario scripts (and local system.ocg) GameScript.Load(ScenarioFile, C4CFN_Script, Config.General.LanguageEx, &ScenarioLangStringTable); + // Map scripts + MapScript.Load(ScenarioFile, C4CFN_MapScript, Config.General.LanguageEx, &ScenarioLangStringTable); // After defs to get overloading priority if (!LoadAdditionalSystemGroup(ScenarioFile)) { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; } @@ -2195,6 +2199,7 @@ bool C4Game::InitScriptEngine() InitCoreFunctionMap(&ScriptEngine); InitObjectFunctionMap(&ScriptEngine); InitGameFunctionMap(&ScriptEngine); + ::MapScript.InitFunctionMap(&ScriptEngine); // system functions: check if system group is open if (!Application.OpenSystemGroup()) diff --git a/src/gamescript/C4Script.h b/src/gamescript/C4Script.h index 86aba78e6..e5e682400 100644 --- a/src/gamescript/C4Script.h +++ b/src/gamescript/C4Script.h @@ -60,6 +60,7 @@ bool C4ValueToMatrix(const C4ValueArray& array, StdMeshMatrix* matrix); #define PSF_InitializeScriptPlayer "~InitializeScriptPlayer" // iPlayer, idTeam #define PSF_PreInitializePlayer "~PreInitializePlayer" // iPlayer #define PSF_InitializePlayerControl "~InitializePlayerControl" // iPlayer, szControlSet, hasKeyboard, hasMouse, hasGamepad +#define PSF_InitializeMap "~InitializeMap" // map #define PSF_RemovePlayer "~RemovePlayer" // iPlayer #define PSF_RelaunchPlayer "~RelaunchPlayer" // iPlayer, iKilledBy #define PSF_Time1 "~Time1" diff --git a/src/graphics/CSurface8.h b/src/graphics/CSurface8.h index b362f223a..ac7ffc944 100644 --- a/src/graphics/CSurface8.h +++ b/src/graphics/CSurface8.h @@ -45,6 +45,11 @@ public: // set pix in local copy... if (Bits) Bits[iY*Pitch+iX]=byCol; } + void _SetPix(int iX, int iY, BYTE byCol) + { + // set pix in local copy without bounds or surface checks + Bits[iY*Pitch+iX]=byCol; + } BYTE GetPix(int iX, int iY) // get pixel { if (iX<0 || iY<0 || iX>=Wdt || iY>=Hgt) return 0; diff --git a/src/landscape/C4Landscape.cpp b/src/landscape/C4Landscape.cpp index fcf54ac5f..96f8e853c 100644 --- a/src/landscape/C4Landscape.cpp +++ b/src/landscape/C4Landscape.cpp @@ -59,6 +59,7 @@ #include #include #include +#include C4Landscape::C4Landscape() { @@ -1165,17 +1166,20 @@ bool C4Landscape::Init(C4Group &hGroup, bool fOverloadCurrent, bool fLoadSky, bo if ((sfcMap=GroupReadSurface8(hGroup, C4CFN_Map))) if (!fLandscapeModeSet) Mode=C4LSC_Static; - // dynamic map from file + // dynamic map from Landscape.txt if (!sfcMap) if ((sfcMap=CreateMapS2(hGroup))) if (!fLandscapeModeSet) Mode=C4LSC_Dynamic; + // script may create or edit map + if (MapScript.InitializeMap(hGroup, &sfcMap)) + if (!fLandscapeModeSet) Mode=C4LSC_Dynamic; + // Dynamic map by scenario if (!sfcMap && !fOverloadCurrent) if ((sfcMap=CreateMap())) if (!fLandscapeModeSet) Mode=C4LSC_Dynamic; - // No map failure if (!sfcMap) { diff --git a/src/landscape/C4MapCreatorS2.cpp b/src/landscape/C4MapCreatorS2.cpp index e4cdb4c71..ca9642f22 100644 --- a/src/landscape/C4MapCreatorS2.cpp +++ b/src/landscape/C4MapCreatorS2.cpp @@ -709,12 +709,7 @@ void C4MCMap::Default() // inherited C4MCOverlay::Default(); // size by landscape def - Wdt=MapCreator->Landscape->MapWdt.Evaluate(); - Hgt=MapCreator->Landscape->MapHgt.Evaluate(); - // map player extend - MapCreator->PlayerCount = Max(MapCreator->PlayerCount, 1); - if (MapCreator->Landscape->MapPlayerExtend) - Wdt = Min(Wdt * Min(MapCreator->PlayerCount, (int) C4S_MaxMapPlayerExtend), (int) MapCreator->Landscape->MapWdt.Max); + MapCreator->Landscape->GetMapSize(Wdt, Hgt, MapCreator->PlayerCount); } bool C4MCMap::RenderTo(BYTE *pToBuf, int32_t iPitch) diff --git a/src/landscape/C4MapScript.cpp b/src/landscape/C4MapScript.cpp new file mode 100644 index 000000000..b2448c68f --- /dev/null +++ b/src/landscape/C4MapScript.cpp @@ -0,0 +1,586 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000 Matthes Bender + * Copyright (c) 2013 Sven Eberhardt + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +/* Handles scripted map creation */ + +#include +#include +#include +#include +#include +#include + +C4MapScriptAlgo *FnParAlgo(C4PropList *algo_par); + +static const char *DrawFn_Transparent_Name = "Transparent"; +static const char *DrawFn_Sky_Name = "Sky"; +static const char *DrawFn_Background_Name = "Background"; +static const char *DrawFn_Liquid_Name = "Liquid"; +static const char *DrawFn_Solid_Name = "Solid"; + +int32_t FnParTexCol(C4String *mattex, int32_t default_col = -1) +{ + // Return index of material-texture definition for a single color + // Defaults to underground (tunnel background) color. Prefix material with ^ to get overground (sky background) color. + if (!mattex || !mattex->GetCStr()) return default_col; + if (mattex->GetData() == DrawFn_Transparent_Name) return 0; + if (mattex->GetData() == DrawFn_Sky_Name) return IFT; + const char *cmattex = mattex->GetCStr(); + int32_t ift = IFT; + if (*cmattex == '^') { ift=0; ++cmattex; } + int32_t col = ::TextureMap.GetIndexMatTex(cmattex); + return col ? col|ift : default_col; +} + +void C4MapScriptMatTexMask::UnmaskSpec(C4String *spec) +{ + // Mask all indices of material-texture definitions + // Possible definitions: + // Material-Texture - Given material-texture combination (both sky and tunnel background) + // Material - All defined default textures of given material + // * - All materials + // Sky - Index IFT + // Transparent - Index 0 + // Background - All tunnel materials plus sky + // Liquid - All liquid materials + // Solid - All solid materials + // Possible modifiers: + // ^Material - Given material only with sky background + // &Material - Given material only with tunnel background + // ~Material - Inverse of given definition; i.e. everything except Material + if (!spec || !spec->GetCStr()) return; + const char *cspec = spec->GetCStr(); + bool invert=false, bgsky=false, bgtunnel=false, prefix_done=false; + while (*cspec) + { + switch (*cspec) + { + case '~': invert=!invert; break; + case '^': bgsky=true; break; + case '&': bgtunnel=true; break; + default: prefix_done=true; break; + } + if (prefix_done) break; + ++cspec; + } + std::vector mat_mask(IFT, false); + if (SEqual(cspec, DrawFn_Transparent_Name)) + { + // "Transparent" is zero index. Force to non-IFT + mat_mask[0] = true; + bgsky = true; bgtunnel = false; + } + else if (SEqual(cspec, DrawFn_Sky_Name)) + { + // Sky material: Force to IFT + mat_mask[0] = true; + bgsky = false; bgtunnel = true; + } + else if (SEqual(cspec, DrawFn_Background_Name)) + { + // All background materials + for (int32_t i=0; imask. Apply bgsky, bgtunnel and invert. + for (int32_t i=0; iGetSize(); ++i) + { + C4String *smask = arr->GetItem(i).getStr(); + if (!smask) throw new 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 new C4AulExecError("MatTexMask expected string or array of strings."); + // nil defaults to everything except index zero unmasked + mask = std::vector(256, true); + mask[0] = false; + } + } +} + + +bool FnParRect(C4MapScriptLayer *layer, C4ValueArray *rect, C4Rect *rc_bounds) +{ + // Convert rect parameter passed to script function to C4Rect structure + // and makes sure it is completely contained in bounding rectangle of layer + // rect==NULL defaults to bounding rectangle of layer + *rc_bounds = layer->GetBounds(); + if (!rect) return true; // nil is OK for rect parameter. Defaults to bounds rectangle + if (rect->GetSize() != 4) return false; + rc_bounds->Intersect(C4Rect(rect->GetItem(0).getInt(), rect->GetItem(1).getInt(), rect->GetItem(2).getInt(), rect->GetItem(3).getInt())); + return true; +} + +static bool FnLayerDraw(C4PropList * _this, C4String *mattex, C4PropList *mask_algo, C4ValueArray *rect) +{ + // Layer script function: Draw material mattex in shape of mask_algo in _this layer within bounds given by rect + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + int32_t icol = FnParTexCol(mattex); + if (!layer || icol<0) return false; + C4Rect rcBounds; + if (!FnParRect(layer, rect, &rcBounds)) return false; + std::auto_ptr algo(FnParAlgo(mask_algo)); + return layer->Fill(icol, rcBounds, algo.get()); +} + +static bool FnLayerBlit(C4PropList * _this, C4PropList *mask_algo, C4ValueArray *rect) +{ + // Layer script function: Blit mask_algo onto surface of _this within bounds given by rect + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return false; + C4Rect rcBounds; + if (!FnParRect(layer, rect, &rcBounds)) return false; + std::auto_ptr algo(FnParAlgo(mask_algo)); + if (!algo.get()) return false; + return layer->Blit(rcBounds, algo.get()); +} + +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 + int32_t icol = FnParTexCol(mattex_fill, 0); + if (icol<0) throw new 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 new 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 (icol) layer->Fill(icol, layer->GetBounds(), NULL); + return layer; +} + +static C4PropList *FnLayerDuplicate(C4PropList * _this, const C4Value &mask_spec, C4ValueArray *rect) +{ + // Layer script function: Create a copy of _this layer within bounds. If mask_spec is specified, copy only materials selected by mask spec + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return NULL; + C4MapScriptMap *map = layer->GetMap(); + if (!map) return NULL; + C4MapScriptMatTexMask mat_mask(mask_spec); + C4Rect src_rect; + if (!FnParRect(layer, rect, &src_rect)) return NULL; + if (!src_rect.Wdt || !src_rect.Hgt) return NULL; + C4MapScriptLayer *new_layer = map->CreateLayer(src_rect.Wdt, src_rect.Hgt); + new_layer->Blit(layer, src_rect, mat_mask, 0,0); + return new_layer; +} + +static int32_t FnLayerGetPixel(C4PropList * _this, int32_t x, int32_t y) +{ + // Layer script function: Query pixel at position x,y from _this layer + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return 0; + return layer->GetPix(x,y,0); +} + +static bool FnLayerSetPixel(C4PropList * _this, int32_t x, int32_t y, int32_t to_value) +{ + // Layer script function: Set pixel at position x,y to to_value in _this layer + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return false; + if (!Inside(to_value, 0, 255)) throw new C4AulExecError(FormatString("MapLayer::SetPixel: TRying to set invalid pixel value %d.", (int)to_value).getData()); + return layer->SetPix(x,y,to_value); +} + +static int32_t FnLayerGetPixelCount(C4PropList * _this, const C4Value &mask_spec, C4ValueArray *rect) +{ + // Layer script function: Count all pixels within rect that match mask_spec specification + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return -1; + C4MapScriptMatTexMask mat_mask(mask_spec); + C4Rect check_rect; + if (!FnParRect(layer, rect, &check_rect)) return -1; + return layer->GetPixCount(check_rect, mask_spec); +} + +static bool FnLayerResize(C4PropList * _this, int32_t new_wdt, int32_t new_hgt) +{ + // Layer script function: Recreate layer in new size. Resulting layer is empty (color 0) + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + // safety + if (!layer || new_wdt<=0 || new_hgt<=0) return false; + // recreate surface in new size + layer->ClearSurface(); + return layer->CreateSurface(new_wdt, new_hgt); +} + +static bool FnLayerFindPosition(C4PropList * _this, C4PropList *out_pos, const C4Value &mask_spec, C4ValueArray *rect, int32_t max_tries) +{ + // Layer script function: Find a position (x,y) that has a color matching mask_spec. Set resulting position as X,Y properties in out_pos prop list + C4MapScriptLayer *layer = _this->GetMapScriptLayer(); + if (!layer) return NULL; + C4MapScriptMatTexMask mat_mask(mask_spec); + C4Rect search_rect; + if (!FnParRect(layer, rect, &search_rect)) return NULL; + int32_t x,y; bool result; + if (!max_tries) max_tries = 500; + if (result = layer->FindPos(search_rect, mat_mask, &x, &y, max_tries)) + { + if (out_pos && !out_pos->IsFrozen()) + { + out_pos->SetProperty(P_X, C4VInt(x)); + out_pos->SetProperty(P_Y, C4VInt(y)); + } + } + return result; +} + +C4MapScriptLayer::C4MapScriptLayer(C4PropList *prototype, C4MapScriptMap *map) : C4PropListNumbered(prototype), surface(NULL), surface_owned(false), map(map) +{ + // It seems like numbered PropLists need a number. I don't know why. + AcquireNumber(); +} + +bool C4MapScriptLayer::CreateSurface(int32_t wdt, int32_t hgt) +{ + // Create new surface of given size. Surface is filled with color 0 + ClearSurface(); + if (wdt<=0 || hgt<=0) return false; + surface = new CSurface8; + surface_owned = true; + if (!surface->Create(wdt, hgt)) + { + ClearSurface(); + return false; + } + UpdateSurfaceSize(); + return true; +} + +void C4MapScriptLayer::ClearSurface() +{ + // Delete surface if owned or just set to zero if unowned + if (surface_owned) delete surface; + surface=NULL; surface_owned=false; + // if there is no surface, width and height parameters are undefined. no need to update them. +} + +void C4MapScriptLayer::UpdateSurfaceSize() +{ + // Called when surface size changes: Update internal property values + if (surface) + { + SetProperty(P_Wdt, C4VInt(surface->Wdt)); + SetProperty(P_Hgt, C4VInt(surface->Hgt)); + } +} + +void C4MapScriptLayer::ConvertSkyToTransparent() +{ + // Convert all sky (color==IFT) pixels to transparent (color==0) + // Needed because C4Landscape map zoom assumes sky to be 0 + if (!HasSurface()) return; + for (int32_t y=0; yHgt; ++y) + for (int32_t x=0; xWdt; ++x) + if (surface->_GetPix(x,y) == IFT) + surface->_SetPix(x,y, 0); +} + +C4Rect C4MapScriptLayer::GetBounds() const +{ + // Return bounding rectangle of surface. Surface always starts at 0,0. + return surface ? C4Rect(0,0,surface->Wdt,surface->Hgt) : C4Rect(); +} + +bool C4MapScriptLayer::Fill(int col, const C4Rect &rcBounds, const C4MapScriptAlgo *algo) +{ + // safety + if (!HasSurface()) return false; + assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=surface->Wdt && rcBounds.y+rcBounds.Hgt<=surface->Hgt); + // set all non-masked pixels within bounds that fulfill algo + for (int32_t y=rcBounds.y; y_SetPix(x,y,col); + return true; +} + +bool C4MapScriptLayer::Blit(const C4Rect &rcBounds, const C4MapScriptAlgo *algo) +{ + // safety + if (!HasSurface()) return false; + assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=surface->Wdt && rcBounds.y+rcBounds.Hgt<=surface->Hgt); + assert(algo); + // set all pixels within bounds by algo, if algo is not transparent + uint8_t col; + for (int32_t y=rcBounds.y; y_SetPix(x,y,col); + return true; +} + +bool C4MapScriptLayer::Blit(const C4MapScriptLayer *src, const C4Rect &src_rect, const C4MapScriptMatTexMask &col_mask, int32_t tx, int32_t ty) +{ + // safety + assert(src); + if (!HasSurface() || !src->HasSurface()) return false; + // cannot assert this, because C4Rect::Contains(C4Rect &) has an off-by-one-error which I don't dare to fix right now + // TODO: Fix C4Rect::Contains and check if the sector code still works + // assert(src->GetBounds().Contains(src_rect)); + // copy all pixels that aren't masked + uint8_t col; + for (int32_t y=src_rect.y; ysurface->_GetPix(x,y))) + surface->_SetPix(x-src_rect.x+tx,y-src_rect.y+ty,col); + return true; +} + +int32_t C4MapScriptLayer::GetPixCount(const C4Rect &rcBounds, const C4MapScriptMatTexMask &col_mask) +{ + // safety + if (!HasSurface()) return 0; + assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=surface->Wdt && rcBounds.y+rcBounds.Hgt<=surface->Hgt); + // count matching pixels in rect + int32_t count = 0; + for (int32_t y=rcBounds.y; y_GetPix(x,y)); + return count; +} + +bool C4MapScriptLayer::FindPos(const C4Rect &search_rect, const C4MapScriptMatTexMask &col_mask, int32_t *out_x, int32_t *out_y, int32_t max_tries) +{ + // safety + if (!HasSurface() || search_rect.Wdt<=0 || search_rect.Hgt<=0) return false; + // Search random positions + for (int32_t i=0; i_GetPix(x,y))) { *out_x=x; *out_y=y; return true; } + } + // Nothing found yet: Start at a random position and search systemically + // (this guantuess to find a pixel if there is one, but favours border pixels) + int32_t sx=search_rect.x + Random(search_rect.Wdt); + int32_t sy=search_rect.y + Random(search_rect.Hgt); + for (int32_t x=sx; xWdt; ++x) + if (col_mask(surface->_GetPix(x,sy))) { *out_x=x; *out_y=sy; return true; } + for (int32_t y=sy+1; yHgt; ++y) + for (int32_t x=0; xWdt; ++x) + if (col_mask(surface->_GetPix(x,y))) { *out_x=x; *out_y=y; return true; } + for (int32_t y=0; yWdt; ++x) + if (col_mask(surface->_GetPix(x,y))) { *out_x=x; *out_y=y; return true; } + for (int32_t x=0; x_GetPix(x,sy))) { *out_x=x; *out_y=sy; return true; } + // Nothing found + return false; +} + +void C4MapScriptMap::Clear() +{ + // Layers are owned by map. Free them. + for (std::list::iterator i=layers.begin(); i!=layers.end(); ++i) delete *i; + layers.clear(); +} + +C4MapScriptLayer *C4MapScriptMap::CreateLayer(int32_t wdt, int32_t hgt) +{ + // Create layer and register to map. Layer's created by a map are freed when the map is freed. + C4MapScriptLayer *new_layer = new C4MapScriptLayer(MapScript.GetLayerPrototype(), this); + layers.push_back(new_layer); // push before CreateSurface for exception safety + if (!new_layer->CreateSurface(wdt, hgt)) + { + layers.remove(new_layer); + delete new_layer; + return NULL; + } + return new_layer; +} + +C4MapScriptHost::C4MapScriptHost(): LayerPrototype(NULL), MapPrototype(NULL) { } + +C4MapScriptHost::~C4MapScriptHost() { Clear(); } + +void C4MapScriptHost::InitFunctionMap(C4AulScriptEngine *pEngine) +{ + // Register script host. Add Map and MapLayer prototypes, related constants and engine functions + assert(pEngine && pEngine->GetPropList()); + Clear(); + LayerPrototype = C4PropList::NewStatic(NULL, NULL, ::Strings.RegString("MapLayer")); + MapPrototype = C4PropList::NewStatic(LayerPrototype, NULL, ::Strings.RegString("Map")); + LayerPrototype->SetName("MapLayer"); + MapPrototype->SetName("Map"); + ::ScriptEngine.RegisterGlobalConstant("MapLayer", C4VPropList(LayerPrototype)); + ::ScriptEngine.RegisterGlobalConstant("Map", C4VPropList(MapPrototype)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Layer", C4VInt(MAPALGO_Layer)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_RndChecker", C4VInt(MAPALGO_RndChecker)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_And", C4VInt(MAPALGO_And)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Or", C4VInt(MAPALGO_Or)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Xor", C4VInt(MAPALGO_Xor)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Not", C4VInt(MAPALGO_Not)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Scale", C4VInt(MAPALGO_Scale)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Offset", C4VInt(MAPALGO_Offset)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Rect", C4VInt(MAPALGO_Rect)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Ellipsis", C4VInt(MAPALGO_Ellipsis)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Polygon", C4VInt(MAPALGO_Polygon)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Turbulence", C4VInt(MAPALGO_Turbulence)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Border", C4VInt(MAPALGO_Border)); + ::ScriptEngine.RegisterGlobalConstant("MAPALGO_Filter", C4VInt(MAPALGO_Filter)); + Reg2List(pEngine); + AddEngineFunctions(); +} + +void C4MapScriptHost::AddEngineFunctions() +{ + // adds all engine functions to the MapLayer context + ::AddFunc(this, "Draw", FnLayerDraw); + ::AddFunc(this, "Blit", FnLayerBlit); + ::AddFunc(this, "CreateLayer", FnCreateLayer); + ::AddFunc(this, "Duplicate", FnLayerDuplicate); + ::AddFunc(this, "GetPixel", FnLayerGetPixel); + ::AddFunc(this, "SetPixel", FnLayerSetPixel); + ::AddFunc(this, "GetPixelCount", FnLayerGetPixelCount); + ::AddFunc(this, "Resize", FnLayerResize); + ::AddFunc(this, "FindPosition", FnLayerFindPosition); +} + +bool C4MapScriptHost::Load(C4Group & g, const char * f, const char * l, C4LangStringTable * t) +{ + assert(LayerPrototype && MapPrototype); + return C4ScriptHost::Load(g, f, l, t); +} + +void C4MapScriptHost::Clear() +{ + delete LayerPrototype; delete MapPrototype; + LayerPrototype = MapPrototype = NULL; + C4ScriptHost::Clear(); +} + +C4PropListStatic * C4MapScriptHost::GetPropList() +{ + // Scripts are compiled in the MapLayer context so it's possible to use all map drawing functions directly without "map->" prefix + return LayerPrototype; +} + +C4MapScriptMap *C4MapScriptHost::CreateMap() +{ + return new C4MapScriptMap(MapPrototype); +} + +bool C4MapScriptHost::InitializeMap(C4Group &group, CSurface8 **pmap_surface) +{ + // Init scripted map by calling InitializeMap in the proper scripts. If *pmap_surface is given, it will pass the existing map to be modified by script. + assert(pmap_surface); + // Don't bother creating surfaces if the functions aren't defined + if (!LayerPrototype->GetFunc(PSF_InitializeMap)) + { + C4PropList *scen_proplist = ::GameScript.ScenPropList._getPropList(); + if (!scen_proplist || !scen_proplist->GetFunc(PSF_InitializeMap)) return false; + } + // Create proplist as script context + std::auto_ptr map(CreateMap()); + // Drawing on existing map or create new? + if (*pmap_surface) + { + // Existing map + map->SetSurface(*pmap_surface); + } + else + { + // No existing map. Create new. + int32_t map_wdt,map_hgt; + ::Game.C4S.Landscape.GetMapSize(map_wdt, map_hgt, ::Game.StartupPlayerCount); + if (!map->CreateSurface(map_wdt, map_hgt)) return false; + } + C4AulParSet Pars(C4VPropList(map.get())); + C4Value result = map->Call(PSF_InitializeMap, &Pars); + if (!result) result = ::GameScript.Call(PSF_InitializeMap, &Pars); + // Map creation done. + if (result) + { + map->ConvertSkyToTransparent(); + *pmap_surface = map->ReleaseSurface(); + } + return !!result; +} + +C4MapScriptHost MapScript; \ No newline at end of file diff --git a/src/landscape/C4MapScript.h b/src/landscape/C4MapScript.h new file mode 100644 index 000000000..22094d482 --- /dev/null +++ b/src/landscape/C4MapScript.h @@ -0,0 +1,329 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000 Matthes Bender + * Copyright (c) 2013 Sven Eberhardt + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +/* Handles scripted map creation */ + +#ifndef INC_C4MapScript +#define INC_C4MapScript + +#include +#include +#include +#include +#include +#include + +// mattex masks: Array of bools for each possible material-texture index +class C4MapScriptMatTexMask +{ + std::vector mask; // vector of size 256: true means pixel color is let through; false means it is blocked + + void UnmaskSpec(C4String *spec); +public: + C4MapScriptMatTexMask() : mask(256,false) { } + C4MapScriptMatTexMask(const C4Value &spec) : mask(256,false) { Init(spec); } + void Init(const C4Value &spec); + + bool operator()(uint8_t mattex) const { return mask[mattex]; } +}; + +// algorithms may be either indicator functions (int,int)->bool that tell whether a map pixel should be +// set (to be used in C4MapScriptLayer::Fill) or functions (int,int)->int that tell which material should +// be set (to be used in Fill or C4MapScriptLayer::Blit). +class C4MapScriptAlgo +{ +protected: + bool GetXYProps(const C4PropList *props, C4PropertyName k, int32_t *out_xy, bool zero_defaults); +public: + virtual uint8_t operator () (int32_t x, int32_t y) const = 0; + virtual ~C4MapScriptAlgo() {} +}; + +// List of possible algorithms. Also registered as script constants in void C4MapScriptHost::InitFunctionMap. +enum C4MapScriptAlgoType +{ + MAPALGO_None = 0, + + MAPALGO_Layer = 1, + + MAPALGO_RndChecker = 10, + + MAPALGO_Rect = 20, + MAPALGO_Ellipsis = 21, + MAPALGO_Polygon = 22, + + MAPALGO_And = 30, + MAPALGO_Or = 31, + MAPALGO_Not = 32, + MAPALGO_Xor = 33, + + MAPALGO_Offset = 40, + MAPALGO_Scale = 41, + MAPALGO_Rotate = 42, + MAPALGO_Turbulence = 43, + + MAPALGO_Border = 50, + MAPALGO_Filter = 51, +}; + +// MAPALGO_Layer: Just query pixel in layer. Pixels outside the layer range are zero. +class C4MapScriptAlgoLayer : public C4MapScriptAlgo +{ + const class C4MapScriptLayer *layer; +public: + C4MapScriptAlgoLayer(const C4MapScriptLayer *layer) : layer(layer) {} + C4MapScriptAlgoLayer(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_RndChecker: checkerboard on which areas are randomly set or unset +class C4MapScriptAlgoRndChecker : public C4MapScriptAlgo +{ + int32_t seed, set_percentage, checker_wdt, checker_hgt; + bool is_fixed_offset; +public: + C4MapScriptAlgoRndChecker(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Rect: 1 for pixels contained in rect, 0 otherwise +class C4MapScriptAlgoRect : public C4MapScriptAlgo +{ + C4Rect rect; +public: + C4MapScriptAlgoRect(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Ellipsis: 1 for pixels within ellipsis, 0 otherwise +class C4MapScriptAlgoEllipsis : public C4MapScriptAlgo +{ + int32_t cx,cy; + uint32_t wdt,hgt; +public: + C4MapScriptAlgoEllipsis(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Polygon: 1 for pixels within polygon or on border, 0 otherwise +class C4MapScriptAlgoPolygon : public C4MapScriptAlgo +{ + struct Pt { int32_t x,y; }; + std::vector poly; + int32_t wdt; + bool empty; // don't fill inside of polygon + bool open; // don't draw closing segment. only valid if empty=true +public: + C4MapScriptAlgoPolygon(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// base class for algo that takes one or more operands +class C4MapScriptAlgoModifier : public C4MapScriptAlgo +{ +protected: + std::vector operands; +public: + C4MapScriptAlgoModifier(const C4PropList *props, int32_t min_ops=0, int32_t max_ops=0); + virtual ~C4MapScriptAlgoModifier() { Clear(); } + void Clear(); +}; + +// MAPALGO_And: 0 if any of the operands is 0. Otherwise, returns value of last operand. +class C4MapScriptAlgoAnd : public C4MapScriptAlgoModifier +{ +public: + C4MapScriptAlgoAnd(const C4PropList *props) : C4MapScriptAlgoModifier(props) { } + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Or: First nonzero operand +class C4MapScriptAlgoOr : public C4MapScriptAlgoModifier +{ +public: + C4MapScriptAlgoOr(const C4PropList *props) : C4MapScriptAlgoModifier(props) { } + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Not: 1 if operand is 0, 0 otherwise. +class C4MapScriptAlgoNot : public C4MapScriptAlgoModifier +{ +public: + C4MapScriptAlgoNot(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) { } + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Xor: If exactly one of the two operands is nonzero, return it. Otherwise, return zero. +class C4MapScriptAlgoXor : public C4MapScriptAlgoModifier +{ +public: + C4MapScriptAlgoXor(const C4PropList *props) : C4MapScriptAlgoModifier(props,2,2) { } + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Offset: Base layer shifted by ox,oy +class C4MapScriptAlgoOffset : public C4MapScriptAlgoModifier +{ + int32_t ox,oy; +public: + C4MapScriptAlgoOffset(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Scale: Base layer scaled by sx,sy percent from fixed point cx,cy +class C4MapScriptAlgoScale : public C4MapScriptAlgoModifier +{ + int32_t sx,sy,cx,cy; +public: + C4MapScriptAlgoScale(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Rotate: Base layer rotated by angle r around point ox,oy +class C4MapScriptAlgoRotate : public C4MapScriptAlgoModifier +{ + int32_t sr,cr,ox,oy; + enum {Precision=1000}; +public: + C4MapScriptAlgoRotate(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Turbulence: move by a random offset iterations times +class C4MapScriptAlgoTurbulence : public C4MapScriptAlgoModifier +{ + int32_t amp[2], scale[2], seed, iterations; +public: + C4MapScriptAlgoTurbulence(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Border: Border of operand layer +class C4MapScriptAlgoBorder : public C4MapScriptAlgoModifier +{ + int32_t left[2], top[2], right[2], bottom[2]; + void ResolveBorderProps(int32_t *p); +public: + C4MapScriptAlgoBorder(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// MAPALGO_Filter: Original color of operand if it's marked to go through filter. 0 otherwise. +class C4MapScriptAlgoFilter : public C4MapScriptAlgoModifier +{ + C4MapScriptMatTexMask filter; +public: + C4MapScriptAlgoFilter(const C4PropList *props); + + virtual uint8_t operator () (int32_t x, int32_t y) const; +}; + +// layer of a script-controlled map +// IFT can be used to mark Sky +class C4MapScriptLayer : public C4PropListNumbered +{ + CSurface8 *surface; + bool surface_owned; +protected: + class C4MapScriptMap *map; + +public: + C4MapScriptLayer(C4PropList *prototype, C4MapScriptMap *map); + virtual ~C4MapScriptLayer() { ClearSurface(); } + virtual C4MapScriptLayer * GetMapScriptLayer() { return this; } + class C4MapScriptMap *GetMap() { return map; } + + // Surface management + bool CreateSurface(int32_t wdt, int32_t hgt); + void ClearSurface(); + CSurface8 *ReleaseSurface() { surface_owned=false; return surface; } // return surface and mark it as not owned by self + void SetSurface(CSurface8 *s) { ClearSurface(); surface=s; UpdateSurfaceSize(); } + bool HasSurface() const { return surface && surface->Bits; } + + // Size management + void UpdateSurfaceSize(); // updates width and height properties by current surface + C4Rect GetBounds () const; + int32_t GetWdt() const { return surface ? surface->Wdt : 0; } + int32_t GetHgt() const { return surface ? surface->Hgt : 0; } + + // Pixel functions + uint8_t GetPix(int32_t x, int32_t y, uint8_t outside_col) const { return (!HasSurface()||x<0||y<0||x>=surface->Wdt||y>=surface->Hgt) ? outside_col : surface->_GetPix(x,y); } + bool SetPix(int32_t x, int32_t y, uint8_t col) const { if (!HasSurface()||x<0||y<0||x>=surface->Wdt||y>=surface->Hgt) return false; surface->_SetPix(x,y,col); return true; } + bool IsPixMasked(int32_t x, int32_t y) const { return GetPix(x,y,0)!=0; } // masking: If pixel is inside surface and not transparent + void ConvertSkyToTransparent(); // change all pixels that are IFT to 0 + int32_t GetPixCount(const C4Rect &rcBounds, const C4MapScriptMatTexMask &col_mask); // return number of pixels that match mask + + // Drawing functions + bool Fill(int col, const C4Rect &rcBounds, const C4MapScriptAlgo *algo); + bool Blit(const C4Rect &rcBounds, const C4MapScriptAlgo *algo); + bool Blit(const C4MapScriptLayer *src, const C4Rect &src_rect, const C4MapScriptMatTexMask &col_mask, int32_t tx, int32_t ty); + + // Search functions + bool FindPos(const C4Rect &search_rect, const C4MapScriptMatTexMask &col_mask, int32_t *out_x, int32_t *out_y, int32_t max_tries); +}; + +class C4MapScriptMap : public C4MapScriptLayer +{ + std::list layers; +public: + C4MapScriptMap(C4PropList *prototype) : C4MapScriptLayer(prototype, NULL) { map=this; } + ~C4MapScriptMap() { Clear(); } + void Clear(); + virtual C4MapScriptMap * GetMapScriptMap() { return this; } + + C4MapScriptLayer *CreateLayer(int32_t wdt, int32_t hgt); +}; + +// Script host for scenario Map.c, parsed in static MapLayer prop list context +// Also hosts engine functions MapLayer prop list. +class C4MapScriptHost : public C4ScriptHost +{ +private: + C4PropListStatic *LayerPrototype, *MapPrototype; + + C4MapScriptMap *CreateMap(); +public: + C4MapScriptHost(); + ~C4MapScriptHost(); + void InitFunctionMap(C4AulScriptEngine *pEngine); + virtual void AddEngineFunctions(); + virtual bool Load(C4Group &, const char *, const char *, C4LangStringTable *); + void Clear(); + virtual C4PropListStatic * GetPropList(); + bool InitializeMap(C4Group &group, CSurface8 **pmap_surface); + C4PropListStatic *GetLayerPrototype() { return LayerPrototype; } +}; + +extern C4MapScriptHost MapScript; + +#endif \ No newline at end of file diff --git a/src/landscape/C4MapScriptAlgo.cpp b/src/landscape/C4MapScriptAlgo.cpp new file mode 100644 index 000000000..3a4522e78 --- /dev/null +++ b/src/landscape/C4MapScriptAlgo.cpp @@ -0,0 +1,482 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000 Matthes Bender + * Copyright (c) 2013 Sven Eberhardt + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +/* Handles scripted map creation */ + +#include +#include +#include + +C4MapScriptAlgo *FnParAlgo(C4PropList *algo_par); + +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 new C4AulExecError(FormatString("C4MapScriptAlgo: Expected either integer or array with two integer elements in property \"%s\".", Strings.P[k]).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; +} + +C4MapScriptAlgoLayer::C4MapScriptAlgoLayer(const C4PropList *props) +{ + // Get MAPALGO_Layer properties + C4PropList *layer_pl = props->GetPropertyPropList(P_Layer); + if (!layer_pl || !(layer = layer_pl->GetMapScriptLayer())) + throw new C4AulExecError("C4MapScriptAlgoLayer: Expected layer in \"Layer\" property."); +} + +uint8_t C4MapScriptAlgoLayer::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Layer at x,y: Just query pixel in layer. Pixels outside the layer range are zero. + return layer->GetPix(x,y,0); +} + +C4MapScriptAlgoRndChecker::C4MapScriptAlgoRndChecker(const C4PropList *props) +{ + // Get MAPALGO_RndChecker properties + seed = props->GetPropertyInt(P_Seed); + if (!seed) seed = Random(65536); + set_percentage = BoundBy(props->GetPropertyInt(P_Ratio), 0, 100); + if (!set_percentage) set_percentage = 50; + checker_wdt = Abs(props->GetPropertyInt(P_Wdt)); + if (!checker_wdt) checker_wdt = 10; + checker_hgt = Abs(props->GetPropertyInt(P_Hgt)); + if (!checker_hgt) checker_hgt = 10; + C4Value is_fixed_offset_v; + if (props->GetProperty(P_FixedOffset, &is_fixed_offset_v)) + is_fixed_offset = is_fixed_offset_v.getBool(); + else + is_fixed_offset = false; +} + +// Division and modulo operators that always round downwards +// Both assuming b>0 +static int32_t divD(int32_t a, int32_t b) { return a/b-(a%b<0); } +static int32_t modD(int32_t a, int32_t b) { return (a>=0)?a%b:b-(-a)%b; } + +// Creates a field of random numbers between 0 and scale-1. Returns the value of the field at position x,y +// Function should be deterministic for the same value of x,y, but should look somewhat random wrt neighbouring values of x,y +int32_t QuerySeededRandomField(int32_t seed, int32_t x, int32_t y, int32_t scale) +{ + return modD((((seed ^ (x*214013))*214013) ^ (y*214013)), scale); +} + +uint8_t C4MapScriptAlgoRndChecker::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_RndChecker at x,y: Query a seeded random field scaled by checker_wdt,checker_hgt + if (!is_fixed_offset) { x+=seed%checker_wdt; y+=((seed*214013)%checker_hgt); } + x = divD(x, checker_wdt); y = divD(y, checker_hgt); + return QuerySeededRandomField(seed, x,y, 100) < set_percentage; +} + +C4MapScriptAlgoRect::C4MapScriptAlgoRect(const C4PropList *props) +{ + // Get MAPALGO_Rect properties + rect = C4Rect(props->GetPropertyInt(P_X), props->GetPropertyInt(P_Y), props->GetPropertyInt(P_Wdt), props->GetPropertyInt(P_Hgt)); +} + +uint8_t C4MapScriptAlgoRect::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Rect at x,y: Return 1 for pixels contained in rect, 0 otherwise + return rect.Contains(x, y); +} + +C4MapScriptAlgoEllipsis::C4MapScriptAlgoEllipsis(const C4PropList *props) +{ + // Get MAPALGO_Ellipsis properties + cx = props->GetPropertyInt(P_X); + cy = props->GetPropertyInt(P_Y); + wdt = Abs(props->GetPropertyInt(P_Wdt)); + hgt = Abs(props->GetPropertyInt(P_Hgt)); + if (!wdt) wdt = 10; + if (!hgt) hgt = wdt; +} + +uint8_t C4MapScriptAlgoEllipsis::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Ellipsis at x,y: Return 1 for pixels within ellipsis, 0 otherwise + // warning: overflows for large values (wdt or hgt >=256) + // but who would draw such large ellipsis anyway? + uint32_t dx = Abs((cx-x)*int32_t(hgt)), dy = Abs((cy-y)*int32_t(wdt)); + return dx*dx+dy*dy < wdt*wdt*hgt*hgt; +} + +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 new C4AulExecError("C4MapScriptAlgoPolygon: Expected two equally sized int arrays in properties \"X\" and \"Y\"."); + poly.resize(ptx->GetSize()); + for (int32_t i=0; iGetSize(); ++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 new C4AulExecError("C4MapScriptAlgoPolygon: Only empty polygons may be open."); +} + +uint8_t C4MapScriptAlgoPolygon::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Polygon at x,y: Return 1 for pixels within the polygon or its borders, 0 otherwise + int32_t crossings = 0; + for (size_t i=0; idy*pdx/pdy); + } + } + // x/y lies inside polygon + return (crossings % 2)==1; +} + +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 || nmax_ops)) + throw new 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; iGetItem(i).getPropList()); + if (!new_algo) throw new C4AulExecError(FormatString("C4MapScriptAlgo: Operand %d in property \"Op\" not valid.", (int)min_ops, (int)max_ops).getData()); + operands[i] = new_algo; + } + } + catch (...) + { + Clear(); + throw; + } +} + +void C4MapScriptAlgoModifier::Clear() +{ + // Child algos are owned by this algo, so delete them + for (std::vector::iterator i=operands.begin(); i != operands.end(); ++i) delete *i; + operands.clear(); +} + +uint8_t C4MapScriptAlgoAnd::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_And at x,y: + // Return 0 if any of the operands is 0. Otherwise, returns value of last operand. + uint8_t val=0; + for (std::vector::const_iterator i=operands.begin(); i != operands.end(); ++i) + if (!(val=(**i)(x,y))) return false; + return val; +} + +uint8_t C4MapScriptAlgoOr::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Or at x,y: + // Return first nonzero operand + uint8_t val; + for (std::vector::const_iterator i=operands.begin(); i != operands.end(); ++i) + if (val=(**i)(x,y)) return val; + // If all operands are zero, return zero. + return 0; +} + +uint8_t C4MapScriptAlgoNot::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Not at x,y: + assert(operands.size()==1); + // Return zero if operand is set and one otherwise + return !(*operands[0])(x,y); +} + +uint8_t C4MapScriptAlgoXor::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Xor at x,y: + assert(operands.size()==2); + // If exactly one of the two operands is nonzero, return it. Otherwise, return zero. + uint8_t v1=(*operands[0])(x,y), v2=(*operands[1])(x,y); + return v1?v2 ?0: v1:v2; +} + +C4MapScriptAlgoOffset::C4MapScriptAlgoOffset(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) +{ + // Get MAPALGO_Offset properties + ox = props->GetPropertyInt(P_OffX); + oy = props->GetPropertyInt(P_OffY); +} + +uint8_t C4MapScriptAlgoOffset::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Offset at x,y: + assert(operands.size()==1); + // Return base layer shifted by ox,oy + return (*operands[0])(x-ox,y-oy); +} + +C4MapScriptAlgoScale::C4MapScriptAlgoScale(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) +{ + // Get MAPALGO_Scale properties + sx = props->GetPropertyInt(P_X); + sy = props->GetPropertyInt(P_Y); + if (!sx) sx=100; + if (!sy) sy=100; + cx = props->GetPropertyInt(P_OffX); + cy = props->GetPropertyInt(P_OffY); +} + +uint8_t C4MapScriptAlgoScale::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Scale at x,y: + assert(operands.size()==1); + // Return base layer scaled by sx,sy percent from fixed point cx,cy + return (*operands[0])((x-cx)*100/sx+cx,(y-cy)*100/sy+cy); +} + +C4MapScriptAlgoRotate::C4MapScriptAlgoRotate(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) +{ + // Get MAPALGO_Rotate properties + int32_t r = props->GetPropertyInt(P_R); + sr=fixtoi(Sin(itofix(r)), Precision); + cr=fixtoi(Cos(itofix(r)), Precision); + ox = props->GetPropertyInt(P_OffX); + oy = props->GetPropertyInt(P_OffY); +} + +uint8_t C4MapScriptAlgoRotate::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Rotate at x,y: + assert(operands.size()==1); + // Return base layer rotated by angle r around point ox,oy + x-=ox; y-=oy; + return (*operands[0])(x*cr/Precision-y*sr/Precision+ox,x*sr/Precision+y*cr/Precision+oy); +} + +C4MapScriptAlgoTurbulence::C4MapScriptAlgoTurbulence(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) +{ + // Get MAPALGO_Turbulence properties + seed = props->GetPropertyInt(P_Seed); + if (!seed) seed = Random(65536); + GetXYProps(props, P_Amplitude, amp, true); + GetXYProps(props, P_Scale, scale, true); + if (!scale[0]) scale[0] = 10; if (!scale[1]) scale[1] = 10; + if (!amp[0] && !amp[1]) { amp[0] = amp[1] = 10; } + iterations = props->GetPropertyInt(P_Iterations); + if (!iterations) iterations = 2; +} + +uint8_t C4MapScriptAlgoTurbulence::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Turbulence at x,y: + // move by a random offset iterations times + assert(operands.size()==1); + int32_t xy[] = {x, y}; + for (int32_t iter=0; iter0) inner=p[i]; else if (p[i]<0) outer=-p[i]; + p[0] = inner; p[1] = outer; +} + +C4MapScriptAlgoBorder::C4MapScriptAlgoBorder(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) +{ + // Get MAPALGO_Border properties + int32_t wdt[2] = {0,0}; + // Parameter Wdt fills all directions + int32_t n_borders = 0; + n_borders += GetXYProps(props, P_Wdt, wdt, false); + for (int32_t i=0; i<2; ++i) left[i]=top[i]=right[i]=bottom[i]=wdt[i]; + // Individual direction parameters + n_borders += GetXYProps(props, P_Left, left, false); + n_borders += GetXYProps(props, P_Top, top, false); + n_borders += GetXYProps(props, P_Right, right, false); + n_borders += GetXYProps(props, P_Bottom, bottom, false); + // Resolve negative/positive values to inner/outer borders + ResolveBorderProps(left); + ResolveBorderProps(top); + ResolveBorderProps(right); + ResolveBorderProps(bottom); + // If nothing was specified, fill all directions with a default: Draw 1px of outer border + if (!n_borders) + { + left[1] = top[1] = right[1] = bottom[1] = 1; + } +} + +uint8_t C4MapScriptAlgoBorder::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Border at x,y: Check if position is at a border of operand layer + // For borders inside operand layer, return the operand material. For outside borders, just return 1. For non-borders, return 0. + // Are we inside or outside? + const C4MapScriptAlgo &l = *operands[0]; + uint8_t mat = l(x,y); + bool inside = !!mat; + if (!mat) mat=1; // return 1 for outside borders + // Check four sideways directions + const int32_t *ymove[] = { top, bottom }, *xmove [] ={ left, right }; + const int32_t d[] = { -1, +1 }; + for (int32_t dir=0; dir<2; ++dir) + { + int32_t hgt = ymove[inside!=!dir][!inside]; + for (int32_t dy=0; dyGetProperty(P_Filter, &spec)) + throw new C4AulExecError("MapScriptAlgoFilter without Filter property."); + filter.Init(spec); +} + +uint8_t C4MapScriptAlgoFilter::operator () (int32_t x, int32_t y) const +{ + // Evaluate MAPALGO_Filter at x,y: + // Return original color if it's marked to go through filter + uint8_t col = (*operands[0])(x,y); + return filter(col) ? col : 0; +} + +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_Turbulence: return new C4MapScriptAlgoTurbulence(algo_par); + case MAPALGO_Border: return new C4MapScriptAlgoBorder(algo_par); + case MAPALGO_Filter: return new C4MapScriptAlgoFilter(algo_par); + default: + throw new C4AulExecError(FormatString("got invalid algo: %d", algo_par->GetPropertyInt(P_Algo)).getData()); + } + return NULL; +} diff --git a/src/lib/C4Rect.h b/src/lib/C4Rect.h index 1627580ed..b0060c257 100644 --- a/src/lib/C4Rect.h +++ b/src/lib/C4Rect.h @@ -43,11 +43,11 @@ public: bool operator ==(const C4Rect &r2) { return !((x-r2.x) | (y-r2.y) | (Wdt-r2.Wdt) | (Hgt-r2.Hgt)); } bool operator !=(const C4Rect &r2) { return 0 != ((x-r2.x) | (y-r2.y) | (Wdt-r2.Wdt) | (Hgt-r2.Hgt)); } - bool Contains(int32_t iX, int32_t iY) + bool Contains(int32_t iX, int32_t iY) const { return iX>=x && iX=y && iY=x && iX+iWdt=y && iY+iHgt #include #include +#include C4AulFunc::C4AulFunc(C4AulScript *pOwner, const char *pName): iRefCnt(0), @@ -56,6 +57,10 @@ StdStrBuf C4AulFunc::GetFullName() { r.Ref("Scenario."); } + else if (Owner == &MapScript) + { + r.Ref("MapLayer."); + } else if (Owner->Engine == Owner) { r.Ref("Global."); diff --git a/src/script/C4AulParse.cpp b/src/script/C4AulParse.cpp index 530de8b3a..0da4b2827 100644 --- a/src/script/C4AulParse.cpp +++ b/src/script/C4AulParse.cpp @@ -771,6 +771,9 @@ bool C4ScriptHost::Preparse() GetPropList()->SetProperty(P_Prototype, C4VPropList(Engine->GetPropList())); LocalValues.Clear(); + // Add any engine functions specific to this script + AddEngineFunctions(); + C4AulParse state(this, C4AulParse::PREPARSER); state.Parse_Script(this); @@ -2879,14 +2882,22 @@ void C4ScriptHost::CopyPropList(C4Set & from, C4PropListStatic * to) case C4V_Function: { C4AulScriptFunc * sf = prop->Value.getFunction()->SFunc(); - //assert(sf->pOrgScript == *s); - C4AulScriptFunc *sfc; - if (sf->pOrgScript != this) - sfc = new C4AulScriptFunc(this, *sf); + if (sf) + { + //assert(sf->pOrgScript == *s); + C4AulScriptFunc *sfc; + if (sf->pOrgScript != this) + sfc = new C4AulScriptFunc(this, *sf); + else + sfc = sf; + sfc->SetOverloaded(to->GetFunc(sf->Name)); + to->SetPropertyByS(prop->Key, C4VFunction(sfc)); + } else - sfc = sf; - sfc->SetOverloaded(to->GetFunc(sf->Name)); - to->SetPropertyByS(prop->Key, C4VFunction(sfc)); + { + // engine function + to->SetPropertyByS(prop->Key, prop->Value); + } } break; case C4V_PropList: diff --git a/src/script/C4PropList.cpp b/src/script/C4PropList.cpp index 7a32313a5..5fe087b67 100644 --- a/src/script/C4PropList.cpp +++ b/src/script/C4PropList.cpp @@ -525,6 +525,20 @@ int32_t C4PropList::GetPropertyInt(C4PropertyName n) const return 0; } +C4PropList *C4PropList::GetPropertyPropList(C4PropertyName n) const +{ + C4String * k = &Strings.P[n]; + if (Properties.Has(k)) + { + return Properties.Get(k).Value.getPropList(); + } + if (prototype) + { + return prototype->GetPropertyPropList(n); + } + return NULL; +} + C4ValueArray * C4PropList::GetProperties() const { C4ValueArray * a; diff --git a/src/script/C4PropList.h b/src/script/C4PropList.h index ee314d2a3..0714b6d1c 100644 --- a/src/script/C4PropList.h +++ b/src/script/C4PropList.h @@ -83,6 +83,8 @@ public: virtual C4Effect * GetEffect(); virtual C4PropListNumbered * GetPropListNumbered(); C4PropList * GetPrototype() const { return prototype; } + virtual class C4MapScriptLayer * GetMapScriptLayer() { return NULL; } + virtual class C4MapScriptMap * GetMapScriptMap() { return NULL; } // saved as a reference to a global constant? virtual class C4PropListStatic * IsStatic() { return NULL; } @@ -114,7 +116,8 @@ public: C4Value Call(const char * k, C4AulParSet *pPars=0, bool fPassErrors=false); C4PropertyName GetPropertyP(C4PropertyName k) const; int32_t GetPropertyInt(C4PropertyName k) const; - bool HasProperty(C4String * k) { return Properties.Has(k); } + C4PropList *GetPropertyPropList(C4PropertyName k) const; + bool HasProperty(C4String * k) const { return Properties.Has(k); } // not allowed on frozen proplists void SetProperty(C4PropertyName k, const C4Value & to) { SetPropertyByS(&Strings.P[k], to); } diff --git a/src/script/C4ScriptHost.h b/src/script/C4ScriptHost.h index e1dc71d8e..ae9f91a8c 100644 --- a/src/script/C4ScriptHost.h +++ b/src/script/C4ScriptHost.h @@ -55,6 +55,7 @@ protected: std::list Includes; // include list std::list Appends; // append list + virtual void AddEngineFunctions() {}; // add any engine functions specific to this script host void CopyPropList(C4Set & from, C4PropListStatic * to); bool ResolveIncludes(C4DefList *rDefs); // resolve includes bool ResolveAppends(C4DefList *rDefs); // resolve appends diff --git a/src/script/C4ScriptStandalone.cpp b/src/script/C4ScriptStandalone.cpp index 82ee04535..f7c03baa1 100644 --- a/src/script/C4ScriptStandalone.cpp +++ b/src/script/C4ScriptStandalone.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #ifdef _DEBUG C4Set C4PropList::PropLists; @@ -100,6 +101,14 @@ void C4Def::IncludeDefinition(C4Def*) {} bool EraseItemSafe(const char *szFilename) {return false;} void AddDbgRec(C4RecordChunkType, const void *, int) {} +C4MapScriptHost MapScript; +C4MapScriptHost::C4MapScriptHost() {} +C4MapScriptHost::~C4MapScriptHost() {} +void C4MapScriptHost::Clear() {} +C4PropListStatic *C4MapScriptHost::GetPropList() {return NULL;} +bool C4MapScriptHost::Load(C4Group &, const char *, const char *, C4LangStringTable *) { return false; } +void C4MapScriptHost::AddEngineFunctions() {} + int c4s_runscript(const char * filename) { InitCoreFunctionMap(&ScriptEngine); diff --git a/src/script/C4StringTable.cpp b/src/script/C4StringTable.cpp index 87055e2bf..b7c5ccf9b 100644 --- a/src/script/C4StringTable.cpp +++ b/src/script/C4StringTable.cpp @@ -150,6 +150,23 @@ C4StringTable::C4StringTable() P[P_ContactIncinerate] = "ContactIncinerate"; P[P_Global] = "Global"; P[P_JumpSpeed] = "JumpSpeed"; + P[P_Algo] = "Algo"; + P[P_Layer] = "Layer"; + P[P_Seed] = "Seed"; + P[P_Ratio] = "Ratio"; + P[P_FixedOffset] = "FixedOffset"; + P[P_Op] = "Op"; + P[P_R] = "R"; + P[P_Scale] = "Scale"; + P[P_Amplitude] = "Amplitude"; + P[P_Iterations] = "Iterations"; + P[P_Empty] = "Empty"; + P[P_Open] = "Open"; + P[P_Left] = "Left"; + P[P_Top] = "Top"; + P[P_Right] = "Right"; + P[P_Bottom] = "Bottom"; + P[P_Filter] = "Filter"; P[DFA_WALK] = "WALK"; P[DFA_FLIGHT] = "FLIGHT"; P[DFA_KNEEL] = "KNEEL"; diff --git a/src/script/C4StringTable.h b/src/script/C4StringTable.h index 52ba80347..3c4ce58fe 100644 --- a/src/script/C4StringTable.h +++ b/src/script/C4StringTable.h @@ -354,6 +354,23 @@ enum C4PropertyName P_ContactIncinerate, P_Global, P_JumpSpeed, + P_Algo, + P_Layer, + P_Seed, + P_Ratio, + P_FixedOffset, + P_Op, + P_R, + P_Scale, + P_Amplitude, + P_Iterations, + P_Empty, + P_Open, + P_Left, + P_Top, + P_Right, + P_Bottom, + P_Filter, // Default Action Procedures DFA_WALK, DFA_FLIGHT,