1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2013-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Handles scripted map creation */
19 
20 #include "C4Include.h"
21 #include "landscape/C4MapScript.h"
22 
23 #include "game/C4GameScript.h"
24 #include "landscape/C4Landscape.h"
25 #include "landscape/C4Texture.h"
26 #include "lib/C4Random.h"
27 #include "script/C4AulDefFunc.h"
28 
29 C4MapScriptAlgo *FnParAlgo(C4PropList *algo_par);
30 
31 static const char *DrawFn_Transparent_Name = "Transparent";
32 static const char *DrawFn_Sky_Name         = "Sky";
33 static const char *DrawFn_Background_Name  = "Background";
34 static const char *DrawFn_Liquid_Name      = "Liquid";
35 static const char *DrawFn_Solid_Name       = "Solid";
36 
TexColSingle(const char * mattex,uint8_t & col)37 bool TexColSingle(const char *mattex, uint8_t& col)
38 {
39 	if (SEqual(mattex, DrawFn_Transparent_Name)) { col = 0; return true; }
40 	if (SEqual(mattex, DrawFn_Sky_Name)) { col = C4M_MaxTexIndex; return true; }
41 
42 	col = ::MapScript.pTexMap->GetIndexMatTex(mattex);
43 	if (col == 0) return false;
44 
45 	return true;
46 }
47 
FnParTexCol(C4String * mattex,uint8_t & fg,uint8_t & bg)48 bool FnParTexCol(C4String *mattex, uint8_t& fg, uint8_t& bg)
49 {
50 	// Return index of material-texture definition for a single color
51 	// Defaults to underground (tunnel background) color. Prefix material with ^ to get overground (sky background) color,
52 	// or specify as mattex1:mattex2 for foreground-background pair.
53 	if (!mattex || !mattex->GetCStr()) return false;
54 
55 	int sep_pos = SCharPos(':', mattex->GetCStr());
56 	if (sep_pos == -1)
57 	{
58 		const char *cmattex = mattex->GetCStr();
59 		bool ift = true;
60 		if (*cmattex == '^') { ift=false; ++cmattex; }
61 
62 		uint8_t col;
63 		if (!TexColSingle(cmattex, col)) return false;
64 
65 		fg = col;
66 		if (ift)
67 			bg = ::MapScript.pTexMap->DefaultBkgMatTex(fg);
68 		else
69 			bg = C4M_MaxTexIndex; // sky
70 
71 		return true;
72 	}
73 	else
74 	{
75 		const char *cmattex = mattex->GetCStr();
76 		std::string fg_mattex(cmattex, cmattex + sep_pos);
77 		std::string bg_mattex(cmattex + sep_pos + 1);
78 
79 		uint8_t fg_col, bg_col;
80 		if (!TexColSingle(fg_mattex.c_str(), fg_col)) return false;
81 		if (!TexColSingle(bg_mattex.c_str(), bg_col)) return false;
82 
83 		fg = fg_col; bg = bg_col;
84 		return true;
85 	}
86 }
87 
UnmaskSpec(C4String * spec)88 void C4MapScriptMatTexMask::UnmaskSpec(C4String *spec)
89 {
90 	// Mask all indices of material-texture definitions
91 	// Possible definitions:
92 	// Material-Texture - Given material-texture combination (both sky and tunnel background)
93 	// Material         - All defined default textures of given material
94 	// *                - All materials including sky
95 	// Sky              - Index C4M_MaxTexIndex
96 	// Transparent      - Index 0
97 	// Background       - All tunnel materials plus sky
98 	// Liquid           - All liquid materials
99 	// Solid            - All solid materials
100 	// Possible modifiers:
101 	// ^Material        - Given material only with sky background
102 	// &Material        - Given material only with tunnel background
103 	// ~Material        - Inverse of given definition; i.e. everything except Material
104 	if (!spec || !spec->GetCStr()) return;
105 	const char *cspec = spec->GetCStr();
106 	bool invert=false, bgsky=false, bgtunnel=false, prefix_done=false;
107 	while (*cspec)
108 	{
109 		switch (*cspec)
110 		{
111 		case '~': invert=!invert; break;
112 		case '^': bgsky=true; break;
113 		case '&': bgtunnel=true; break;
114 		default: prefix_done=true; break;
115 		}
116 		if (prefix_done) break;
117 		++cspec;
118 	}
119 	std::vector<bool> mat_mask(C4M_MaxTexIndex+1, false);
120 	if (SEqual(cspec, DrawFn_Transparent_Name))
121 	{
122 		// "Transparent" is zero index. Force to non-IFT
123 		mat_mask[0] = true;
124 	}
125 	else if (SEqual(cspec, DrawFn_Sky_Name))
126 	{
127 		// Sky material
128 		mat_mask[C4M_MaxTexIndex] = true;
129 	}
130 	else if (SEqual(cspec, DrawFn_Background_Name))
131 	{
132 		// All background materials
133 		for (int32_t i=1; i<C4M_MaxTexIndex; ++i) if (!DensitySemiSolid(Landscape.GetPixDensity(i))) mat_mask[i] = true;
134 		// Background includes sky
135 		mat_mask[C4M_MaxTexIndex] = true;
136 	}
137 	else if (SEqual(cspec, DrawFn_Liquid_Name))
138 	{
139 		// All liquid materials
140 		for (int32_t i=1; i<C4M_MaxTexIndex; ++i) if (DensityLiquid(Landscape.GetPixDensity(i))) mat_mask[i] = true;
141 	}
142 	else if (SEqual(cspec, DrawFn_Solid_Name))
143 	{
144 		// All solid materials
145 		for (int32_t i=1; i<C4M_MaxTexIndex; ++i) if (DensitySolid(Landscape.GetPixDensity(i))) mat_mask[i] = true;
146 	}
147 	else if (SEqual(cspec, "*"))
148 	{
149 		// All materials
150 		for (int32_t i=1; i<C4M_MaxTexIndex; ++i) mat_mask[i] = true;
151 		// Including sky
152 		mat_mask[C4M_MaxTexIndex] = true;
153 	}
154 	else
155 	{
156 		// Specified material
157 		if (SCharCount('-', cspec))
158 		{
159 			// Material+Texture
160 			int32_t col = ::MapScript.pTexMap->GetIndexMatTex(cspec, nullptr, false);
161 			if (col) mat_mask[col] = true;
162 		}
163 		else
164 		{
165 			// Only material: Mask all textures of this material
166 			int32_t mat = ::MapScript.pMatMap->Get(cspec);
167 			if (mat!=MNone)
168 			{
169 				const char *tex_name;
170 				int32_t col;
171 				for (int32_t itex=0; (tex_name=::MapScript.pTexMap->GetTexture(itex)); itex++)
172 					if ((col = ::MapScript.pTexMap->GetIndex(cspec,tex_name,false)))
173 						mat_mask[col] = true;
174 			}
175 		}
176 	}
177 
178 	// 'OR' spec onto this->sky_mask and this->tunnel_mask. Apply bgsky, bgtunnel and invert.
179 	for (int32_t i=0; i<C4M_MaxTexIndex + 1; ++i)
180 	{
181 		if ((mat_mask[i] && (bgsky || !bgtunnel)) != invert)
182 			sky_mask[i] = true;
183 		if ((mat_mask[i] && (!bgsky || bgtunnel)) != invert)
184 			tunnel_mask[i] = true;
185 	}
186 }
187 
Init(const C4Value & spec)188 void C4MapScriptMatTexMask::Init(const C4Value &spec)
189 {
190 	// Mask may be initialized by a simple string or by an array of strings, of which the effects are OR'ed
191 	const C4ValueArray *arr = spec.getArray();
192 	if (arr)
193 	{
194 		// Init by array
195 		for (int32_t i=0; i<arr->GetSize(); ++i)
196 		{
197 			C4String *smask = arr->GetItem(i).getStr();
198 			if (!smask) throw C4AulExecError(FormatString("MatTexMask expected string as %dth element in array.", (int)i).getData());
199 			UnmaskSpec(smask);
200 		}
201 	}
202 	else
203 	{
204 		// Init by string
205 		C4String *smask = spec.getStr();
206 		if (smask)
207 			UnmaskSpec(smask);
208 		else
209 		{
210 			if (spec) throw C4AulExecError("MatTexMask expected string or array of strings.");
211 			// nil defaults to everything except index zero unmasked
212 			sky_mask = std::vector<bool>(256, true);
213 			tunnel_mask = std::vector<bool>(256, true);
214 			sky_mask[0] = false;
215 			tunnel_mask[0] = false;
216 		}
217 	}
218 }
219 
220 
FnParRect(C4MapScriptLayer * layer,C4ValueArray * rect,C4Rect * rc_bounds)221 bool FnParRect(C4MapScriptLayer *layer, C4ValueArray *rect, C4Rect *rc_bounds)
222 {
223 	// Convert rect parameter passed to script function to C4Rect structure
224 	// and makes sure it is completely contained in bounding rectangle of layer
225 	// rect==nullptr defaults to bounding rectangle of layer
226 	*rc_bounds = layer->GetBounds();
227 	if (!rect) return true; // nil is OK for rect parameter. Defaults to bounds rectangle
228 	if (rect->GetSize() != 4) return false;
229 	rc_bounds->Intersect(C4Rect(rect->GetItem(0).getInt(), rect->GetItem(1).getInt(), rect->GetItem(2).getInt(), rect->GetItem(3).getInt()));
230 	return true;
231 }
232 
FnLayerDraw(C4PropList * _this,C4String * mattex,C4PropList * mask_algo,C4ValueArray * rect)233 static bool FnLayerDraw(C4PropList * _this, C4String *mattex, C4PropList *mask_algo, C4ValueArray *rect)
234 {
235 	// Layer script function: Draw material mattex in shape of mask_algo in _this layer within bounds given by rect
236 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
237 	uint8_t fg, bg;
238 	if (!layer || !FnParTexCol(mattex, fg, bg)) return false;
239 	C4Rect rcBounds;
240 	if (!FnParRect(layer, rect, &rcBounds)) return false;
241 	std::unique_ptr<C4MapScriptAlgo> algo(FnParAlgo(mask_algo));
242 	return layer->Fill(fg, bg, rcBounds, algo.get());
243 }
244 
FnLayerBlit(C4PropList * _this,C4PropList * mask_algo,C4ValueArray * rect)245 static bool FnLayerBlit(C4PropList * _this, C4PropList *mask_algo, C4ValueArray *rect)
246 {
247 	// Layer script function: Blit mask_algo onto surface of _this  within bounds given by rect
248 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
249 	if (!layer) return false;
250 	C4Rect rcBounds;
251 	if (!FnParRect(layer, rect, &rcBounds)) return false;
252 	std::unique_ptr<C4MapScriptAlgo> algo(FnParAlgo(mask_algo));
253 	if (!algo.get()) return false;
254 	return layer->Blit(rcBounds, algo.get());
255 }
256 
FnCreateLayer(C4PropList * _this,C4String * mattex_fill,int32_t width,int32_t height)257 static C4PropList *FnCreateLayer(C4PropList * _this, C4String *mattex_fill, int32_t width, int32_t height)
258 {
259 	// Layer script function: Create new layer filled by mattex_fill of size width,height as sub layer of _this map
260 	// Size defaults to _this layer size
261 	uint8_t fg = 0, bg = 0;
262 	if (mattex_fill && mattex_fill->GetCStr())
263 		if (!FnParTexCol(mattex_fill, fg, bg))
264 			throw C4AulExecError(FormatString("CreateLayer: Invalid fill material.").getData());
265 
266 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
267 	if (!layer) return nullptr;
268 	if (!width && !height)
269 	{
270 		width = layer->GetWdt();
271 		height = layer->GetHgt();
272 	}
273 	if (width<=0 || height<=0) throw C4AulExecError(FormatString("CreateLayer: Invalid size (%d*%d).", (int)width, (int)height).getData());
274 	C4MapScriptMap *map = layer->GetMap();
275 	if (!map) return nullptr;
276 	layer = map->CreateLayer(width, height);
277 	if (fg != 0 || bg != 0) layer->Fill(fg, bg, layer->GetBounds(), nullptr);
278 	return layer;
279 }
280 
FnLayerDuplicate(C4PropList * _this,const C4Value & mask_spec,C4ValueArray * rect)281 static C4PropList *FnLayerDuplicate(C4PropList * _this, const C4Value &mask_spec, C4ValueArray *rect)
282 {
283 	// Layer script function: Create a copy of _this layer within bounds. If mask_spec is specified, copy only materials selected by mask spec
284 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
285 	if (!layer) return nullptr;
286 	C4MapScriptMap *map = layer->GetMap();
287 	if (!map) return nullptr;
288 	C4MapScriptMatTexMask mat_mask(mask_spec);
289 	C4Rect src_rect;
290 	if (!FnParRect(layer, rect, &src_rect)) return nullptr;
291 	if (!src_rect.Wdt || !src_rect.Hgt) return nullptr;
292 	C4MapScriptLayer *new_layer = map->CreateLayer(src_rect.Wdt, src_rect.Hgt);
293 	new_layer->Blit(layer, src_rect, mat_mask, 0,0);
294 	return new_layer;
295 }
296 
FnLayerGetMaterialTextureIndex(C4PropList * _this,C4String * mattex)297 static int32_t FnLayerGetMaterialTextureIndex(C4PropList * _this, C4String* mattex)
298 {
299 	if (!mattex) return -1;
300 
301 	uint8_t col;
302 	if (!TexColSingle(mattex->GetCStr(), col))
303 		return -1;
304 
305 	return col;
306 }
307 
FnLayerGetDefaultBackgroundIndex(C4PropList * _this,const C4Value & value)308 static int32_t FnLayerGetDefaultBackgroundIndex(C4PropList * _this, const C4Value &value)
309 {
310 	uint8_t fg;
311 	C4String* str;
312 
313 	if ((str = value.getStr()))
314 	{
315 		if (!TexColSingle(str->GetCStr(), fg))
316 			return -1;
317 	}
318 	else
319 	{
320 		if (!Inside(value.getInt(), 0, 255))
321 			return -1;
322 		fg = value.getInt();
323 	}
324 
325 	return ::MapScript.pTexMap->DefaultBkgMatTex(fg);
326 }
327 
FnLayerGetPixel(C4PropList * _this,int32_t x,int32_t y)328 static int32_t FnLayerGetPixel(C4PropList * _this, int32_t x, int32_t y)
329 {
330 	// Layer script function: Query pixel at position x,y from _this layer
331 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
332 	if (!layer) return 0;
333 	return layer->GetPix(x,y,0);
334 }
335 
FnLayerGetBackPixel(C4PropList * _this,int32_t x,int32_t y)336 static int32_t FnLayerGetBackPixel(C4PropList * _this, int32_t x, int32_t y)
337 {
338 	// Layer script function: Query pixel at position x,y from _this layer
339 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
340 	if (!layer) return 0;
341 	return layer->GetBackPix(x,y,0);
342 }
343 
FnLayerSetPixel(C4PropList * _this,int32_t x,int32_t y,const C4Value & fg_value_c4v,const C4Value & bg_value_c4v)344 static bool FnLayerSetPixel(C4PropList * _this, int32_t x, int32_t y, const C4Value& fg_value_c4v, const C4Value& bg_value_c4v)
345 {
346 	// Layer script function: Set pixel at position x,y to to_value in _this layer
347 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
348 	if (!layer) return false;
349 	uint8_t fg, bg;
350 
351 	if (fg_value_c4v.GetType() == C4V_Nil)
352 	{
353 		fg = layer->GetPix(x,y,0);
354 	}
355 	else
356 	{
357 		const C4Value& val = fg_value_c4v;
358 		C4String *str = val.getStr();
359 		if (str != nullptr)
360 		{
361 			if (!TexColSingle(str->GetCStr(), fg))
362 				throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value.");
363 		}
364 		else
365 		{
366 			if (!Inside(val.getInt(), 0, 255))
367 				throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value.");
368 			fg = val.getInt();
369 		}
370 	}
371 
372 	if (bg_value_c4v.GetType() == C4V_Nil)
373 	{
374 		bg = layer->GetBackPix(x,y,0);
375 	}
376 	else
377 	{
378 		const C4Value& val = bg_value_c4v;
379 		C4String *str = val.getStr();
380 		if (str != nullptr)
381 		{
382 			if (!TexColSingle(str->GetCStr(), bg))
383 				throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value.");
384 		}
385 		else
386 		{
387 			if (!Inside(val.getInt(), 0, 255))
388 				throw C4AulExecError("MapLayer::SetPixel: Trying to set invalid pixel value.");
389 			bg = val.getInt();
390 		}
391 	}
392 
393 	return layer->SetPix(x,y,fg,bg);
394 }
395 
FnLayerGetPixelCount(C4PropList * _this,const C4Value & mask_spec,C4ValueArray * rect)396 static int32_t FnLayerGetPixelCount(C4PropList * _this, const C4Value &mask_spec, C4ValueArray *rect)
397 {
398 	// Layer script function: Count all pixels within rect that match mask_spec specification
399 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
400 	if (!layer) return -1;
401 	C4MapScriptMatTexMask mat_mask(mask_spec);
402 	C4Rect check_rect;
403 	if (!FnParRect(layer, rect, &check_rect)) return -1;
404 	return layer->GetPixCount(check_rect, mask_spec);
405 }
406 
FnLayerResize(C4PropList * _this,int32_t new_wdt,int32_t new_hgt)407 static bool FnLayerResize(C4PropList * _this, int32_t new_wdt, int32_t new_hgt)
408 {
409 	// Layer script function: Recreate layer in new size. Resulting layer is empty (color 0)
410 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
411 	// safety
412 	if (!layer || new_wdt<=0 || new_hgt<=0) return false;
413 	// recreate surface in new size
414 	layer->ClearSurface();
415 	return layer->CreateSurface(new_wdt, new_hgt);
416 }
417 
FnLayerFindPosition(C4PropList * _this,C4PropList * out_pos,const C4Value & mask_spec,C4ValueArray * rect,int32_t max_tries)418 static bool FnLayerFindPosition(C4PropList * _this, C4PropList *out_pos, const C4Value &mask_spec, C4ValueArray *rect, int32_t max_tries)
419 {
420 	// 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
421 	C4MapScriptLayer *layer = _this->GetMapScriptLayer();
422 	if (!layer) return false;
423 	C4MapScriptMatTexMask mat_mask(mask_spec);
424 	C4Rect search_rect;
425 	if (!FnParRect(layer, rect, &search_rect)) return false;
426 	int32_t x,y; bool result;
427 	if (!max_tries) max_tries = 500;
428 	if ((result = layer->FindPos(search_rect, mat_mask, &x, &y, max_tries)))
429 	{
430 		if (out_pos && !out_pos->IsFrozen())
431 		{
432 			out_pos->SetProperty(P_X, C4VInt(x));
433 			out_pos->SetProperty(P_Y, C4VInt(y));
434 		}
435 	}
436 	return result;
437 }
438 
FnLayerCreateMatTexMask(C4PropList * _this,const C4Value & mask_spec)439 static C4ValueArray *FnLayerCreateMatTexMask(C4PropList * _this, const C4Value &mask_spec)
440 {
441 	// layer script function: Generate an array 256 bools representing the given mask_spec
442 	C4MapScriptMatTexMask mat_mask(mask_spec);
443 	C4ValueArray *result = new C4ValueArray(C4M_MaxTexIndex + 1);
444 	for (int32_t i=0; i < C4M_MaxTexIndex + 1; ++i)
445 	{
446 		result->SetItem(i, C4VBool(mat_mask(uint8_t(i), C4M_MaxTexIndex)));
447 	}
448 	return result;
449 }
450 
451 // TODO: CreateBackMatTexMask? Or Return 512 bools?
452 
C4MapScriptLayer(C4PropList * prototype,C4MapScriptMap * map)453 C4MapScriptLayer::C4MapScriptLayer(C4PropList *prototype, C4MapScriptMap *map) : C4PropListNumbered(prototype), map(map)
454 {
455 	// It seems like numbered PropLists need a number. I don't know why.
456 	AcquireNumber();
457 }
458 
CreateSurface(int32_t wdt,int32_t hgt)459 bool C4MapScriptLayer::CreateSurface(int32_t wdt, int32_t hgt)
460 {
461 	// Create new surface of given size. Surface is filled with color 0
462 	ClearSurface();
463 	if (wdt<=0 || hgt<=0) return false;
464 	fg_surface = std::make_unique<CSurface8>();
465 	bg_surface = std::make_unique<CSurface8>();
466 	if (!fg_surface->Create(wdt, hgt) || !bg_surface->Create(wdt, hgt))
467 	{
468 		ClearSurface();
469 		return false;
470 	}
471 	UpdateSurfaceSize();
472 	return true;
473 }
474 
ClearSurface()475 void C4MapScriptLayer::ClearSurface()
476 {
477 	fg_surface.reset(); bg_surface.reset();
478 	// if there is no surface, width and height parameters are undefined. no need to update them.
479 }
480 
UpdateSurfaceSize()481 void C4MapScriptLayer::UpdateSurfaceSize()
482 {
483 	// Called when surface size changes: Update internal property values
484 	if (fg_surface)
485 	{
486 		SetProperty(P_Wdt, C4VInt(fg_surface->Wdt));
487 		SetProperty(P_Hgt, C4VInt(fg_surface->Hgt));
488 	}
489 }
490 
ConvertSkyToTransparent()491 void C4MapScriptLayer::ConvertSkyToTransparent()
492 {
493 	// Convert all sky (color==C4M_MaxTexIndex) pixels to transparent (color==0)
494 	// Needed because C4Landscape map zoom assumes sky to be 0
495 	if (!HasSurface()) return;
496 	for (int32_t y=0; y<fg_surface->Hgt; ++y)
497 	{
498 		for (int32_t x=0; x<fg_surface->Wdt; ++x)
499 		{
500 			if (fg_surface->_GetPix(x,y) == C4M_MaxTexIndex)
501 				fg_surface->_SetPix(x,y, 0);
502 
503 			if (bg_surface->_GetPix(x,y) == C4M_MaxTexIndex)
504 				bg_surface->_SetPix(x,y, 0);
505 		}
506 	}
507 }
508 
GetBounds() const509 C4Rect C4MapScriptLayer::GetBounds() const
510 {
511 	// Return bounding rectangle of surface. Surface always starts at 0,0.
512 	return fg_surface ? C4Rect(0,0,fg_surface->Wdt,fg_surface->Hgt) : C4Rect();
513 }
514 
Fill(uint8_t fg,uint8_t bg,const C4Rect & rcBounds,const C4MapScriptAlgo * algo)515 bool C4MapScriptLayer::Fill(uint8_t fg, uint8_t bg, const C4Rect &rcBounds, const C4MapScriptAlgo *algo)
516 {
517 	// safety
518 	uint8_t temp_fg, temp_bg;
519 	if (!HasSurface()) return false;
520 	assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=fg_surface->Wdt && rcBounds.y+rcBounds.Hgt<=fg_surface->Hgt);
521 	// set all non-masked pixels within bounds that fulfill algo
522 	for (int32_t y=rcBounds.y; y<rcBounds.y+rcBounds.Hgt; ++y)
523 		for (int32_t x=rcBounds.x; x<rcBounds.x+rcBounds.Wdt; ++x)
524 			if (!algo || (*algo)(x,y,temp_fg,temp_bg))
525 			{
526 				fg_surface->_SetPix(x,y,fg);
527 				bg_surface->_SetPix(x,y,bg);
528 			}
529 
530 	return true;
531 }
532 
Blit(const C4Rect & rcBounds,const C4MapScriptAlgo * algo)533 bool C4MapScriptLayer::Blit(const C4Rect &rcBounds, const C4MapScriptAlgo *algo)
534 {
535 	// safety
536 	if (!HasSurface()) return false;
537 	assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=fg_surface->Wdt && rcBounds.y+rcBounds.Hgt<=fg_surface->Hgt);
538 	assert(algo);
539 	// set all pixels within bounds by algo, if algo is not transparent
540 	uint8_t fg, bg;
541 	for (int32_t y=rcBounds.y; y<rcBounds.y+rcBounds.Hgt; ++y)
542 		for (int32_t x=rcBounds.x; x<rcBounds.x+rcBounds.Wdt; ++x)
543 			if (((*algo)(x,y,fg,bg)))
544 			{
545 				if (fg) fg_surface->_SetPix(x,y,fg);
546 				if (bg) bg_surface->_SetPix(x,y,bg);
547 			}
548 	return true;
549 }
550 
Blit(const C4MapScriptLayer * src,const C4Rect & src_rect,const C4MapScriptMatTexMask & col_mask,int32_t tx,int32_t ty)551 bool C4MapScriptLayer::Blit(const C4MapScriptLayer *src, const C4Rect &src_rect, const C4MapScriptMatTexMask &col_mask, int32_t tx, int32_t ty)
552 {
553 	// safety
554 	assert(src);
555 	if (!HasSurface() || !src->HasSurface()) return false;
556 	// cannot assert this, because C4Rect::Contains(C4Rect &) has an off-by-one-error which I don't dare to fix right now
557 	// TODO: Fix C4Rect::Contains and check if the sector code still works
558 	// assert(src->GetBounds().Contains(src_rect));
559 	// copy all pixels that aren't masked
560 	uint8_t fg, bg;
561 	for (int32_t y=src_rect.y; y<src_rect.y+src_rect.Hgt; ++y)
562 		for (int32_t x=src_rect.x; x<src_rect.x+src_rect.Wdt; ++x)
563 		{
564 			fg = src->fg_surface->_GetPix(x, y);
565 			bg = src->bg_surface->_GetPix(x, y);
566 
567 			if (col_mask(fg, bg))
568 			{
569 				fg_surface->_SetPix(x-src_rect.x+tx,y-src_rect.y+ty,fg);
570 				bg_surface->_SetPix(x-src_rect.x+tx,y-src_rect.y+ty,bg);
571 			}
572 		}
573 	return true;
574 }
575 
GetPixCount(const C4Rect & rcBounds,const C4MapScriptMatTexMask & col_mask)576 int32_t C4MapScriptLayer::GetPixCount(const C4Rect &rcBounds, const C4MapScriptMatTexMask &col_mask)
577 {
578 	// safety
579 	if (!HasSurface()) return 0;
580 	assert(rcBounds.x>=0 && rcBounds.y>=0 && rcBounds.x+rcBounds.Wdt<=fg_surface->Wdt && rcBounds.y+rcBounds.Hgt<=fg_surface->Hgt);
581 	// count matching pixels in rect
582 	int32_t count = 0;
583 	for (int32_t y=rcBounds.y; y<rcBounds.y+rcBounds.Hgt; ++y)
584 		for (int32_t x=rcBounds.x; x<rcBounds.x+rcBounds.Wdt; ++x)
585 			count += col_mask(fg_surface->_GetPix(x,y), bg_surface->_GetPix(x, y));
586 	return count;
587 }
588 
FindPos(const C4Rect & search_rect,const C4MapScriptMatTexMask & col_mask,int32_t * out_x,int32_t * out_y,int32_t max_tries)589 bool C4MapScriptLayer::FindPos(const C4Rect &search_rect, const C4MapScriptMatTexMask &col_mask, int32_t *out_x, int32_t *out_y, int32_t max_tries)
590 {
591 	// safety
592 	if (!HasSurface() || search_rect.Wdt<=0 || search_rect.Hgt<=0) return false;
593 	// Search random positions
594 	for (int32_t i=0; i<max_tries; ++i)
595 	{
596 		int32_t x=search_rect.x + Random(search_rect.Wdt);
597 		int32_t y=search_rect.y + Random(search_rect.Hgt);
598 		if (col_mask(fg_surface->_GetPix(x,y), bg_surface->_GetPix(x,y))) { *out_x=x; *out_y=y; return true; }
599 	}
600 	// Nothing found yet: Start at a random position and search systemically
601 	// (this guantuess to find a pixel if there is one, but favours border pixels)
602 	int32_t sx=search_rect.x + Random(search_rect.Wdt);
603 	int32_t sy=search_rect.y + Random(search_rect.Hgt);
604 	for (int32_t x = sx; x < search_rect.x + search_rect.Wdt; ++x)
605 		if (col_mask(fg_surface->_GetPix(x,sy), bg_surface->_GetPix(x,sy))) { *out_x=x; *out_y=sy; return true; }
606 	for (int32_t y = sy + 1; y<search_rect.y + search_rect.Hgt; ++y)
607 		for (int32_t x = search_rect.x; x < search_rect.x + search_rect.Wdt; ++x)
608 			if (col_mask(fg_surface->_GetPix(x,y), bg_surface->_GetPix(x,y))) { *out_x=x; *out_y=y; return true; }
609 	for (int32_t y = search_rect.y; y<sy; ++y)
610 		for (int32_t x = search_rect.x; x < search_rect.x + search_rect.Wdt; ++x)
611 			if (col_mask(fg_surface->_GetPix(x,y), bg_surface->_GetPix(x,y))) { *out_x=x; *out_y=y; return true; }
612 	for (int32_t x = search_rect.x; x<sx; ++x)
613 		if (col_mask(fg_surface->_GetPix(x,sy), bg_surface->_GetPix(x,sy))) { *out_x=x; *out_y=sy; return true; }
614 	// Nothing found
615 	return false;
616 }
617 
Clear()618 void C4MapScriptMap::Clear()
619 {
620 	// Layers are owned by map. Free them.
621 	for (auto & layer : layers) delete layer;
622 	layers.clear();
623 }
624 
CreateLayer(int32_t wdt,int32_t hgt)625 C4MapScriptLayer *C4MapScriptMap::CreateLayer(int32_t wdt, int32_t hgt)
626 {
627 	// Create layer and register to map. Layer's created by a map are freed when the map is freed.
628 	C4MapScriptLayer *new_layer = new C4MapScriptLayer(MapScript.GetLayerPrototype(), this);
629 	layers.push_back(new_layer); // push before CreateSurface for exception safety
630 	if (!new_layer->CreateSurface(wdt, hgt))
631 	{
632 		layers.remove(new_layer);
633 		delete new_layer;
634 		return nullptr;
635 	}
636 	return new_layer;
637 }
638 
639 C4MapScriptHost::C4MapScriptHost() = default;
640 
~C4MapScriptHost()641 C4MapScriptHost::~C4MapScriptHost() { Clear(); }
642 
InitFunctionMap(C4AulScriptEngine * pEngine)643 void C4MapScriptHost::InitFunctionMap(C4AulScriptEngine *pEngine)
644 {
645 	// Register script host. Add Map and MapLayer prototypes, related constants and engine functions
646 	assert(pEngine && pEngine->GetPropList());
647 	Clear();
648 	LayerPrototype = new C4PropListStaticMember(nullptr, nullptr, ::Strings.RegString("MapLayer"));
649 	MapPrototype = new C4PropListStaticMember(LayerPrototype, nullptr, ::Strings.RegString("Map"));
650 	LayerPrototype->SetName("MapLayer");
651 	MapPrototype->SetName("Map");
652 	::ScriptEngine.RegisterGlobalConstant("MapLayer", C4VPropList(LayerPrototype));
653 	::ScriptEngine.RegisterGlobalConstant("Map", C4VPropList(MapPrototype));
654 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Layer", C4VInt(MAPALGO_Layer));
655 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_RndChecker", C4VInt(MAPALGO_RndChecker));
656 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_And", C4VInt(MAPALGO_And));
657 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Or", C4VInt(MAPALGO_Or));
658 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Xor", C4VInt(MAPALGO_Xor));
659 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Not", C4VInt(MAPALGO_Not));
660 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Scale", C4VInt(MAPALGO_Scale));
661 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Rotate", C4VInt(MAPALGO_Rotate));
662 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Offset", C4VInt(MAPALGO_Offset));
663 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Rect", C4VInt(MAPALGO_Rect));
664 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Ellipsis", C4VInt(MAPALGO_Ellipsis));
665 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Polygon", C4VInt(MAPALGO_Polygon));
666 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Lines", C4VInt(MAPALGO_Lines));
667 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Turbulence", C4VInt(MAPALGO_Turbulence));
668 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Border", C4VInt(MAPALGO_Border));
669 	::ScriptEngine.RegisterGlobalConstant("MAPALGO_Filter", C4VInt(MAPALGO_Filter));
670 	Reg2List(pEngine);
671 	AddEngineFunctions();
672 }
673 
AddEngineFunctions()674 void C4MapScriptHost::AddEngineFunctions()
675 {
676 	// adds all engine functions to the MapLayer context
677 	C4PropListStatic * p = GetPropList();
678 	::AddFunc(p, "Draw", FnLayerDraw);
679 	::AddFunc(p, "Blit", FnLayerBlit);
680 	::AddFunc(p, "CreateLayer", FnCreateLayer);
681 	::AddFunc(p, "Duplicate", FnLayerDuplicate);
682 	::AddFunc(p, "GetMaterialTextureIndex", FnLayerGetMaterialTextureIndex);
683 	::AddFunc(p, "GetDefaultBackgroundIndex", FnLayerGetDefaultBackgroundIndex);
684 	::AddFunc(p, "GetPixel", FnLayerGetPixel);
685 	::AddFunc(p, "GetBackPixel", FnLayerGetBackPixel);
686 	::AddFunc(p, "SetPixel", FnLayerSetPixel);
687 	::AddFunc(p, "GetPixelCount", FnLayerGetPixelCount);
688 	::AddFunc(p, "Resize", FnLayerResize);
689 	::AddFunc(p, "FindPosition", FnLayerFindPosition);
690 	::AddFunc(p, "CreateMatTexMask", FnLayerCreateMatTexMask);
691 }
692 
Load(C4Group & g,const char * f,const char * l,C4LangStringTable * t)693 bool C4MapScriptHost::Load(C4Group & g, const char * f, const char * l, C4LangStringTable * t)
694 {
695 	assert(LayerPrototype && MapPrototype);
696 	return C4ScriptHost::Load(g, f, l, t);
697 }
698 
LoadData(const char * f,const char * d,C4LangStringTable * t)699 bool C4MapScriptHost::LoadData(const char * f, const char * d, C4LangStringTable * t)
700 {
701 	assert(LayerPrototype && MapPrototype);
702 	return C4ScriptHost::LoadData(f, d, t);
703 }
704 
Clear()705 void C4MapScriptHost::Clear()
706 {
707 	C4ScriptHost::Clear();
708 	delete LayerPrototype; delete MapPrototype;
709 	LayerPrototype = MapPrototype = nullptr;
710 }
711 
GetPropList()712 C4PropListStatic * C4MapScriptHost::GetPropList()
713 {
714 	// Scripts are compiled in the MapLayer context so it's possible to use all map drawing functions directly without "map->" prefix
715 	return LayerPrototype;
716 }
717 
CreateMap()718 C4MapScriptMap *C4MapScriptHost::CreateMap()
719 {
720 	return new C4MapScriptMap(MapPrototype);
721 }
722 
InitializeMap(C4SLandscape * pLandscape,C4TextureMap * pTexMap,C4MaterialMap * pMatMap,uint32_t iPlayerCount,std::unique_ptr<CSurface8> * pmap_fg_surface,std::unique_ptr<CSurface8> * pmap_bg_surface)723 bool C4MapScriptHost::InitializeMap(C4SLandscape *pLandscape, C4TextureMap *pTexMap, C4MaterialMap *pMatMap, uint32_t iPlayerCount, std::unique_ptr<CSurface8> *pmap_fg_surface, std::unique_ptr <CSurface8>* pmap_bg_surface)
724 {
725 	// 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.
726 	assert(pmap_fg_surface);
727 	assert(pmap_bg_surface);
728 
729 	this->pTexMap = pTexMap;
730 	this->pMatMap = pMatMap;
731 	// Don't bother creating surfaces if the functions aren't defined
732 	if (!LayerPrototype->GetFunc(PSF_InitializeMap))
733 	{
734 		C4PropList *scen_proplist = ::GameScript.ScenPropList._getPropList();
735 		if (!scen_proplist || !scen_proplist->GetFunc(PSF_InitializeMap)) return false;
736 	}
737 	// Create proplist as script context
738 	std::unique_ptr<C4MapScriptMap> map(CreateMap());
739 
740 	// Drawing on existing map or create new?
741 	if (*pmap_fg_surface && *pmap_bg_surface)
742 	{
743 		// Existing map
744 		map->SetSurfaces(std::move(*pmap_fg_surface), std::move(*pmap_bg_surface));
745 	}
746 	else
747 	{
748 		assert(!*pmap_fg_surface && !*pmap_bg_surface);
749 		// No existing map. Create new.
750 		int32_t map_wdt,map_hgt;
751 		pLandscape->GetMapSize(map_wdt, map_hgt, iPlayerCount);
752 		if (!map->CreateSurface(map_wdt, map_hgt)) return false;
753 	}
754 	C4AulParSet Pars(C4VPropList(map.get()));
755 	C4Value result = map->Call(PSF_InitializeMap, &Pars);
756 	if (!result) result = ::GameScript.Call(PSF_InitializeMap, &Pars);
757 	// Map creation done.
758 	if (result)
759 	{
760 		map->ConvertSkyToTransparent();
761 	}
762 	std::tie(*pmap_fg_surface, *pmap_bg_surface) = map->ReleaseSurfaces();
763 	return !!result;
764 }
765 
766 C4MapScriptHost MapScript;
767