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) 2009-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 /* Material definitions used by the landscape */
19 
20 #include "C4Include.h"
21 #include "landscape/C4Material.h"
22 
23 #include "c4group/C4Components.h"
24 #include "c4group/C4Group.h"
25 #include "editor/C4ToolsDlg.h" // For C4TLS_MatSky...
26 #include "game/C4Physics.h" // For GravAccel
27 #include "landscape/C4PXS.h"
28 #include "landscape/C4Texture.h"
29 #include "landscape/C4Landscape.h"
30 #include "lib/C4Random.h"
31 #include "platform/C4SoundSystem.h"
32 #include "script/C4Aul.h"
33 #include "script/C4Effect.h"
34 
35 
36 int32_t MVehic=MNone,MHalfVehic=MNone,MTunnel=MNone,MWater=MNone,MEarth=MNone;
37 BYTE MCVehic=0;
38 BYTE MCHalfVehic=0;
39 // -------------------------------------- C4MaterialReaction
40 
41 
42 struct ReactionFuncMapEntry { const char *szRFName; C4MaterialReactionFunc pFunc; };
43 
44 const ReactionFuncMapEntry ReactionFuncMap[] =
45 {
46 	{ "Script",  &C4MaterialMap::mrfScript },
47 	{ "Convert", &C4MaterialMap::mrfConvert},
48 	{ "Poof",    &C4MaterialMap::mrfPoof },
49 	{ "Corrode", &C4MaterialMap::mrfCorrode },
50 	{ "Insert",  &C4MaterialMap::mrfInsert },
51 	{ nullptr, &C4MaterialReaction::NoReaction }
52 };
53 
54 
CompileFunc(StdCompiler * pComp)55 void C4MaterialReaction::CompileFunc(StdCompiler *pComp)
56 {
57 	if (pComp->isDeserializer()) pScriptFunc = nullptr;
58 	// compile reaction func ptr
59 	StdStrBuf sReactionFuncName;
60 	int32_t i=0; while (ReactionFuncMap[i].szRFName && (ReactionFuncMap[i].pFunc != pFunc)) ++i;
61 	sReactionFuncName = ReactionFuncMap[i].szRFName;
62 	pComp->Value(mkNamingAdapt(mkParAdapt(sReactionFuncName, StdCompiler::RCT_IdtfAllowEmpty),   "Type",                     StdCopyStrBuf() ));
63 	i=0; while (ReactionFuncMap[i].szRFName && !SEqual(ReactionFuncMap[i].szRFName, sReactionFuncName.getData())) ++i;
64 	pFunc = ReactionFuncMap[i].pFunc;
65 	// compile the rest
66 	pComp->Value(mkNamingAdapt(mkParAdapt(TargetSpec, StdCompiler::RCT_All),          "TargetSpec",               StdCopyStrBuf() ));
67 	pComp->Value(mkNamingAdapt(mkParAdapt(ScriptFunc, StdCompiler::RCT_IdtfAllowEmpty),          "ScriptFunc",               StdCopyStrBuf() ));
68 	pComp->Value(mkNamingAdapt(iExecMask,           "ExecMask",                 ~0u             ));
69 	pComp->Value(mkNamingAdapt(fReverse,            "Reverse",                  false           ));
70 	pComp->Value(mkNamingAdapt(fInverseSpec,        "InverseSpec",              false           ));
71 	pComp->Value(mkNamingAdapt(fInsertionCheck,     "CheckSlide",               true            ));
72 	pComp->Value(mkNamingAdapt(iDepth,              "Depth",                    0               ));
73 	pComp->Value(mkNamingAdapt(mkParAdapt(sConvertMat, StdCompiler::RCT_IdtfAllowEmpty),         "ConvertMat",               StdCopyStrBuf() ));
74 	pComp->Value(mkNamingAdapt(iCorrosionRate,      "CorrosionRate",            100             ));
75 }
76 
77 
ResolveScriptFuncs(const char * szMatName)78 void C4MaterialReaction::ResolveScriptFuncs(const char *szMatName)
79 {
80 	// get script func for script-defined behaviour
81 	if (pFunc == &C4MaterialMap::mrfScript)
82 	{
83 		pScriptFunc = ::ScriptEngine.GetPropList()->GetFunc(this->ScriptFunc.getData());
84 		if (!pScriptFunc)
85 			DebugLogF(R"(Error getting function "%s" for Material reaction of "%s")", this->ScriptFunc.getData(), szMatName);
86 	}
87 	else
88 		pScriptFunc = nullptr;
89 }
90 
91 // -------------------------------------- C4MaterialCore
92 
C4MaterialCore()93 C4MaterialCore::C4MaterialCore()
94 {
95 	Clear();
96 }
97 
Clear()98 void C4MaterialCore::Clear()
99 {
100 	CustomReactionList.clear();
101 	sTextureOverlay.Clear();
102 	sPXSGfx.Clear();
103 	sBlastShiftTo.Clear();
104 	sInMatConvert.Clear();
105 	sInMatConvertTo.Clear();
106 	sBelowTempConvertTo.Clear();
107 	sAboveTempConvertTo.Clear();
108 	*Name='\0';
109 	MapChunkType = C4M_Flat;
110 	Density = 0;
111 	Friction = 0;
112 	DigFree = 0;
113 	BlastFree = 0;
114 	Dig2Object = C4ID::None;
115 	Dig2ObjectRatio = 0;
116 	Dig2ObjectCollect = 0;
117 	Blast2Object = C4ID::None;
118 	Blast2ObjectRatio = 0;
119 	Blast2PXSRatio = 0;
120 	Instable = 0;
121 	MaxAirSpeed = 0;
122 	MaxSlide = 0;
123 	WindDrift = 0;
124 	Inflammable = 0;
125 	Incendiary = 0;
126 	Extinguisher = 0;
127 	Corrosive = 0;
128 	Corrode = 0;
129 	Soil = 0;
130 	Placement = 0;
131 	Light = 0;
132 	OverlayType = 0;
133 	PXSGfxRt.Default();
134 	PXSGfxSize = 0;
135 	InMatConvertDepth = 0;
136 	BelowTempConvert = 0;
137 	BelowTempConvertDir = 0;
138 	AboveTempConvert = 0;
139 	AboveTempConvertDir = 0;
140 	TempConvStrength = 0;
141 	MinHeightCount = 0;
142 	SplashRate=10;
143 	KeepSinglePixels=false;
144 	AnimationSpeed = 20;
145 	LightAngle = 255;
146 	for (int i = 0; i < 3; i++) {
147 		LightEmit[i] = 0;
148 		LightSpot[i] = 16;
149 	}
150 	MinShapeOverlap = 25;
151 }
152 
Default()153 void C4MaterialCore::Default()
154 {
155 	Clear();
156 }
157 
Load(C4Group & hGroup,const char * szEntryName)158 bool C4MaterialCore::Load(C4Group &hGroup,
159                           const char *szEntryName)
160 {
161 	StdStrBuf Source;
162 	if (!hGroup.LoadEntryString(szEntryName,&Source))
163 		return false;
164 	StdStrBuf Name = hGroup.GetFullName() + DirSep + szEntryName;
165 	if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(*this, Source, Name.getData()))
166 		return false;
167 	// adjust placement, if not specified
168 	if (!Placement)
169 	{
170 		if (DensitySolid(Density))
171 		{
172 			Placement=30;
173 			if (!DigFree) Placement+=20;
174 			if (!BlastFree) Placement+=10;
175 		}
176 		else if (DensityLiquid(Density))
177 			Placement=10;
178 		else Placement=5;
179 	}
180 	return true;
181 }
182 
CompileFunc(StdCompiler * pComp)183 void C4MaterialCore::CompileFunc(StdCompiler *pComp)
184 {
185 	assert(pComp->hasNaming());
186 	if (pComp->isDeserializer()) Clear();
187 	pComp->Name("Material");
188 	pComp->Value(mkNamingAdapt(toC4CStr(Name),      "Name",                ""));
189 
190 	const StdEnumEntry<C4MaterialCoreShape> Shapes[] =
191 	{
192 		{ "Flat",     C4M_Flat },
193 		{ "TopFlat",  C4M_TopFlat },
194 		{ "Smooth",   C4M_Smooth },
195 		{ "Rough",    C4M_Rough },
196 		{ "Octagon",  C4M_Octagon },
197 		{ "Smoother", C4M_Smoother },
198 		{ nullptr, C4M_Flat }
199 	};
200 	pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(MapChunkType, Shapes),
201 	                                                "Shape",               C4M_Flat));
202 	pComp->Value(mkNamingAdapt(Density,             "Density",             0));
203 	pComp->Value(mkNamingAdapt(Friction,            "Friction",            0));
204 	pComp->Value(mkNamingAdapt(DigFree,             "DigFree",             0));
205 	pComp->Value(mkNamingAdapt(BlastFree,           "BlastFree",           0));
206 	pComp->Value(mkNamingAdapt(Blast2Object,        "Blast2Object",        C4ID::None));
207 	pComp->Value(mkNamingAdapt(Dig2Object,          "Dig2Object",          C4ID::None));
208 	pComp->Value(mkNamingAdapt(Dig2ObjectRatio,     "Dig2ObjectRatio",     0));
209 	pComp->Value(mkNamingAdapt(Dig2ObjectCollect,   "Dig2ObjectCollect",   0));
210 	pComp->Value(mkNamingAdapt(Blast2ObjectRatio,   "Blast2ObjectRatio",   0));
211 	pComp->Value(mkNamingAdapt(Blast2PXSRatio,      "Blast2PXSRatio",      0));
212 	pComp->Value(mkNamingAdapt(Instable,            "Instable",            0));
213 	pComp->Value(mkNamingAdapt(MaxAirSpeed,         "MaxAirSpeed",         0));
214 	pComp->Value(mkNamingAdapt(MaxSlide,            "MaxSlide",            0));
215 	pComp->Value(mkNamingAdapt(WindDrift,           "WindDrift",           0));
216 	pComp->Value(mkNamingAdapt(Inflammable,         "Inflammable",         0));
217 	if (pComp->isDeserializer())
218 	{
219 		// The value used to have a wrong spelling ("Incindiary"). If there's
220 		// no "Incendiary" value, use the wrong spelling instead
221 		try
222 		{
223 			pComp->Value(mkNamingAdapt(Incendiary, "Incendiary"));
224 		}
225 		catch (StdCompiler::NotFoundException *ex)
226 		{
227 			delete ex;
228 			pComp->Value(mkNamingAdapt(Incendiary, "Incindiary", 0));
229 		}
230 	}
231 	else
232 	{
233 		// When serializing, write both spellings because some script might be
234 		// calling GetMaterialVal with the wrong one
235 		pComp->Value(mkNamingAdapt(Incendiary, "Incendiary"));
236 		pComp->Value(mkNamingAdapt(Incendiary, "Incindiary"));
237 	}
238 	pComp->Value(mkNamingAdapt(Corrode,             "Corrode",             0));
239 	pComp->Value(mkNamingAdapt(Corrosive,           "Corrosive",           0));
240 	pComp->Value(mkNamingAdapt(Extinguisher,        "Extinguisher",        0));
241 	pComp->Value(mkNamingAdapt(Soil,                "Soil",                0));
242 	pComp->Value(mkNamingAdapt(Placement,           "Placement",           0));
243 	pComp->Value(mkNamingAdapt(Light,               "Light",               0));
244 	pComp->Value(mkNamingAdapt(mkParAdapt(sTextureOverlay, StdCompiler::RCT_IdtfAllowEmpty),
245 	                                                "TextureOverlay",      ""));
246 	pComp->Value(mkNamingAdapt(OverlayType,         "OverlayType",         0));
247 	pComp->Value(mkNamingAdapt(mkParAdapt(sPXSGfx, StdCompiler::RCT_IdtfAllowEmpty),
248 	                                                "PXSGfx",              ""));
249 	pComp->Value(mkNamingAdapt(PXSGfxRt,            "PXSGfxRt",            TargetRect0));
250 	pComp->Value(mkNamingAdapt(PXSGfxSize,          "PXSGfxSize",          PXSGfxRt.Wdt));
251 	pComp->Value(mkNamingAdapt(TempConvStrength,    "TempConvStrength",    0));
252 	pComp->Value(mkNamingAdapt(mkParAdapt(sBlastShiftTo, StdCompiler::RCT_IdtfAllowEmpty),
253 	                                                "BlastShiftTo",        ""));
254 	pComp->Value(mkNamingAdapt(mkParAdapt(sInMatConvert, StdCompiler::RCT_IdtfAllowEmpty),
255 	                                                "InMatConvert",        ""));
256 	pComp->Value(mkNamingAdapt(mkParAdapt(sInMatConvertTo, StdCompiler::RCT_IdtfAllowEmpty),
257 	                                                "InMatConvertTo",      ""));
258 	pComp->Value(mkNamingAdapt(InMatConvertDepth,   "InMatConvertDepth",   0));
259 	pComp->Value(mkNamingAdapt(AboveTempConvert,    "AboveTempConvert",    0));
260 	pComp->Value(mkNamingAdapt(AboveTempConvertDir, "AboveTempConvertDir", 0));
261 	pComp->Value(mkNamingAdapt(mkParAdapt(sAboveTempConvertTo, StdCompiler::RCT_IdtfAllowEmpty),
262 	                                                "AboveTempConvertTo",  ""));
263 	pComp->Value(mkNamingAdapt(BelowTempConvert,    "BelowTempConvert",    0));
264 	pComp->Value(mkNamingAdapt(BelowTempConvertDir, "BelowTempConvertDir", 0));
265 	pComp->Value(mkNamingAdapt(mkParAdapt(sBelowTempConvertTo, StdCompiler::RCT_IdtfAllowEmpty),
266 	                                                "BelowTempConvertTo",  ""));
267 	pComp->Value(mkNamingAdapt(MinHeightCount,      "MinHeightCount",      0));
268 	pComp->Value(mkNamingAdapt(SplashRate,          "SplashRate",          10));
269 	pComp->Value(mkNamingAdapt(KeepSinglePixels,    "KeepSinglePixels",    false));
270 	pComp->Value(mkNamingAdapt(AnimationSpeed,      "AnimationSpeed",      100));
271 	pComp->Value(mkNamingAdapt(LightAngle,          "LightAngle",          255));
272 	pComp->Value(mkNamingAdapt(mkArrayAdaptDM(LightEmit, 0), "LightEmit"));
273 	pComp->Value(mkNamingAdapt(mkArrayAdaptDM(LightSpot, 16),"LightSpot"));
274 	pComp->Value(mkNamingAdapt(MinShapeOverlap,     "MinShapeOverlap",     25));
275 	pComp->NameEnd();
276 	// material reactions
277 	pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(CustomReactionList),
278 	                                                "Reaction",            std::vector<C4MaterialReaction>()));
279 }
280 
281 
282 // -------------------------------------- C4Material
283 
C4Material()284 C4Material::C4Material()
285 {
286 	BlastShiftTo=0;
287 	InMatConvertTo=MNone;
288 	BelowTempConvertTo=0;
289 	AboveTempConvertTo=0;
290 }
291 
UpdateScriptPointers()292 void C4Material::UpdateScriptPointers()
293 {
294 	for (auto & i : CustomReactionList)
295 		i.ResolveScriptFuncs(Name);
296 }
297 
298 
299 // -------------------------------------- C4MaterialMap
300 
301 
C4MaterialMap()302 C4MaterialMap::C4MaterialMap() : DefReactConvert(&mrfConvert), DefReactPoof(&mrfPoof), DefReactCorrode(&mrfCorrode), DefReactIncinerate(&mrfIncinerate), DefReactInsert(&mrfInsert)
303 {
304 	Default();
305 }
306 
307 
~C4MaterialMap()308 C4MaterialMap::~C4MaterialMap()
309 {
310 	Clear();
311 }
312 
Clear()313 void C4MaterialMap::Clear()
314 {
315 	if (Map) delete [] Map; Map=nullptr; Num=0;
316 	delete [] ppReactionMap; ppReactionMap = nullptr;
317 }
318 
Load(C4Group & hGroup)319 int32_t C4MaterialMap::Load(C4Group &hGroup)
320 {
321 	char entryname[256+1];
322 
323 	// Determine number of materials in files
324 	int32_t mat_num=hGroup.EntryCount(C4CFN_MaterialFiles);
325 
326 	// Allocate new map
327 	C4Material *pNewMap = new C4Material [mat_num + Num];
328 	if (!pNewMap) return 0;
329 
330 	// Load material cores to map
331 	hGroup.ResetSearch(); int32_t cnt=0;
332 	while (hGroup.FindNextEntry(C4CFN_MaterialFiles,entryname))
333 	{
334 		if (cnt >= mat_num) {
335 			Log("Internal Error: More materials loaded than expected. Make sure your material file names are unique (ignoring case).");
336 			break;
337 		}
338 		// Load mat
339 		if (!pNewMap[cnt].Load(hGroup,entryname))
340 			{ delete [] pNewMap; return 0; }
341 		// A new material?
342 		if (Get(pNewMap[cnt].Name) == MNone)
343 			cnt++;
344 	}
345 
346 	// Take over old materials.
347 	for (int32_t i = 0; i < Num; i++)
348 	{
349 		pNewMap[cnt+i] = Map[i];
350 	}
351 	delete [] Map;
352 	Map = pNewMap;
353 
354 	// set material number
355 	Num+=cnt;
356 
357 	return cnt;
358 }
359 
HasMaterials(C4Group & hGroup) const360 bool C4MaterialMap::HasMaterials(C4Group &hGroup) const
361 {
362 	return !!hGroup.EntryCount(C4CFN_MaterialFiles);
363 }
364 
Get(const char * szMaterial)365 int32_t C4MaterialMap::Get(const char *szMaterial)
366 {
367 	int32_t cnt;
368 	for (cnt=0; cnt<Num; cnt++)
369 		if (SEqualNoCase(szMaterial,Map[cnt].Name))
370 			return cnt;
371 	return MNone;
372 }
373 
374 
CrossMapMaterials(const char * szEarthMaterial)375 bool C4MaterialMap::CrossMapMaterials(const char* szEarthMaterial) // Called after load
376 {
377 	// Check material number
378 	if (::MaterialMap.Num>C4MaxMaterial)
379 		{ LogFatal(LoadResStr("IDS_PRC_TOOMANYMATS")); return false; }
380 	// build reaction function map
381 	delete [] ppReactionMap;
382 	typedef C4MaterialReaction * C4MaterialReactionPtr;
383 	ppReactionMap = new C4MaterialReactionPtr[(Num+1)*(Num+1)];
384 	for (int32_t iMatPXS=-1; iMatPXS<Num; iMatPXS++)
385 	{
386 		C4Material *pMatPXS = (iMatPXS+1) ? Map+iMatPXS : nullptr;
387 		for (int32_t iMatLS=-1; iMatLS<Num; iMatLS++)
388 		{
389 			C4MaterialReaction *pReaction = nullptr;
390 			C4Material *pMatLS  = ( iMatLS+1) ? Map+ iMatLS : nullptr;
391 			// natural stuff: material conversion here?
392 			if (pMatPXS && pMatPXS->sInMatConvert.getLength() && SEqualNoCase(pMatPXS->sInMatConvert.getData(), pMatLS ? pMatLS->Name : C4TLS_MatSky))
393 				pReaction = &DefReactConvert;
394 			// non-sky reactions
395 			else if (pMatPXS && pMatLS)
396 			{
397 				// incindiary vs extinguisher
398 				if ((pMatPXS->Incendiary && pMatLS->Extinguisher) || (pMatPXS->Extinguisher && pMatLS->Incendiary))
399 					pReaction = &DefReactPoof;
400 				// incindiary vs inflammable
401 				else if ((pMatPXS->Incendiary && pMatLS->Inflammable) || (pMatPXS->Inflammable && pMatLS->Incendiary))
402 					pReaction = &DefReactIncinerate;
403 				// corrosive vs corrode
404 				else if (pMatPXS->Corrosive && pMatLS->Corrode)
405 					pReaction = &DefReactCorrode;
406 				// liquid hitting liquid or solid: Material insertion
407 				else if (DensityLiquid(MatDensity(iMatPXS)) && DensitySemiSolid(MatDensity(iMatLS)))
408 					pReaction = &DefReactInsert;
409 				// solid hitting solid: Material insertion
410 				else if (DensitySolid(MatDensity(iMatPXS)) && DensitySolid(MatDensity(iMatLS)))
411 					pReaction = &DefReactInsert;
412 			}
413 			// assign the function; or nullptr for no reaction
414 			SetMatReaction(iMatPXS, iMatLS, pReaction);
415 		}
416 	}
417 	// reset max shape size
418 	max_shape_width=max_shape_height=0;
419 	// material-specific initialization
420 	int32_t cnt;
421 	for (cnt=0; cnt<Num; cnt++)
422 	{
423 		C4Material *pMat = Map+cnt;
424 		const char *szTextureOverlay = nullptr;
425 		// newgfx: init pattern
426 		if (Map[cnt].sTextureOverlay.getLength())
427 			if (::TextureMap.GetTexture(Map[cnt].sTextureOverlay.getLength()))
428 			{
429 				szTextureOverlay = Map[cnt].sTextureOverlay.getData();
430 				// backwards compatibility: if a pattern was specified although the no-pattern flag was set, overwrite that flag
431 				if (Map[cnt].OverlayType & C4MatOv_None)
432 				{
433 					DebugLogF("Error in overlay of material %s: Flag C4MatOv_None ignored because a custom overlay (%s) was specified!", Map[cnt].Name, szTextureOverlay);
434 					Map[cnt].OverlayType &= ~C4MatOv_None;
435 				}
436 			}
437 		// default to first texture in texture map
438 		int iTexMapIx;
439 		if (!szTextureOverlay && (iTexMapIx = ::TextureMap.GetIndex(Map[cnt].Name, nullptr, false)))
440 			szTextureOverlay = TextureMap.GetEntry(iTexMapIx)->GetTextureName();
441 		// default to smooth
442 		if (!szTextureOverlay)
443 			szTextureOverlay = "none";
444 		// search/create entry in texmap
445 		Map[cnt].DefaultMatTex = ::TextureMap.GetIndex(Map[cnt].Name, szTextureOverlay, true,
446 		                         FormatString("DefaultMatTex of mat %s", Map[cnt].Name).getData());
447 		// init PXS facet
448 		C4Surface * sfcTexture;
449 		C4Texture * Texture;
450 		if (Map[cnt].sPXSGfx.getLength())
451 			if ((Texture=::TextureMap.GetTexture(Map[cnt].sPXSGfx.getData())))
452 				if ((sfcTexture=Texture->Surface32))
453 					Map[cnt].PXSFace.Set(sfcTexture, Map[cnt].PXSGfxRt.x, Map[cnt].PXSGfxRt.y, Map[cnt].PXSGfxRt.Wdt, Map[cnt].PXSGfxRt.Hgt);
454 		// evaluate reactions for that material
455 		for (auto & iRCnt : pMat->CustomReactionList)
456 		{
457 			C4MaterialReaction *pReact = &iRCnt;
458 			if (pReact->sConvertMat.getLength()) pReact->iConvertMat = Get(pReact->sConvertMat.getData()); else pReact->iConvertMat = -1;
459 			// evaluate target spec
460 			int32_t tmat;
461 			if (MatValid(tmat=Get(pReact->TargetSpec.getData())))
462 			{
463 				// single material target
464 				if (pReact->fInverseSpec)
465 					for (int32_t cnt2=-1; cnt2<Num; cnt2++) {
466 						if (cnt2!=tmat)
467 							SetMatReaction(cnt, cnt2, pReact);
468 						else
469 							SetMatReaction(cnt, tmat, pReact);
470 					}
471 			}
472 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "All"))
473 			{
474 				// add to all materials, including sky
475 				if (!pReact->fInverseSpec) for (int32_t cnt2=-1; cnt2<Num; cnt2++) SetMatReaction(cnt, cnt2, pReact);
476 			}
477 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Solid"))
478 			{
479 				// add to all solid materials
480 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
481 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (DensitySolid(Map[cnt2].Density) != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
482 			}
483 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "SemiSolid"))
484 			{
485 				// add to all semisolid materials
486 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
487 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (DensitySemiSolid(Map[cnt2].Density) != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
488 			}
489 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Background"))
490 			{
491 				// add to all BG materials, including sky
492 				if (!pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
493 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Density != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
494 			}
495 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Sky"))
496 			{
497 				// add to sky
498 				if (!pReact->fInverseSpec)
499 					SetMatReaction(cnt, -1, pReact);
500 				else
501 					for (int32_t cnt2=0; cnt2<Num; cnt2++) SetMatReaction(cnt, cnt2, pReact);
502 			}
503 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Incendiary") || SEqualNoCase(pReact->TargetSpec.getData(), "Incindiary"))
504 			{
505 				// add to all incendiary materials
506 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
507 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Incendiary == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
508 			}
509 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Extinguisher"))
510 			{
511 				// add to all incendiary materials
512 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
513 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Extinguisher == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
514 			}
515 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Inflammable"))
516 			{
517 				// add to all incendiary materials
518 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
519 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Inflammable == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
520 			}
521 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Corrosive"))
522 			{
523 				// add to all incendiary materials
524 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
525 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Corrosive == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
526 			}
527 			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Corrode"))
528 			{
529 				// add to all incendiary materials
530 				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
531 				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Corrode == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
532 			}
533 		}
534 	}
535 	// second loop (DefaultMatTex is needed by GetIndexMatTex)
536 	for (cnt=0; cnt<Num; cnt++)
537 	{
538 		if (Map[cnt].sBlastShiftTo.getLength())
539 			Map[cnt].BlastShiftTo=::TextureMap.GetIndexMatTex(Map[cnt].sBlastShiftTo.getData(), nullptr, true, FormatString("BlastShiftTo of mat %s", Map[cnt].Name).getData());
540 		if (Map[cnt].sInMatConvertTo.getLength())
541 			Map[cnt].InMatConvertTo=Get(Map[cnt].sInMatConvertTo.getData());
542 		if (Map[cnt].sBelowTempConvertTo.getLength())
543 			Map[cnt].BelowTempConvertTo=::TextureMap.GetIndexMatTex(Map[cnt].sBelowTempConvertTo.getData(), nullptr, true, FormatString("BelowTempConvertTo of mat %s", Map[cnt].Name).getData());
544 		if (Map[cnt].sAboveTempConvertTo.getLength())
545 			Map[cnt].AboveTempConvertTo=::TextureMap.GetIndexMatTex(Map[cnt].sAboveTempConvertTo.getData(), nullptr, true, FormatString("AboveTempConvertTo of mat %s", Map[cnt].Name).getData());
546 	}
547 
548 	// Get hardcoded system material indices
549 	const C4TexMapEntry* earth_entry = ::TextureMap.GetEntry(::TextureMap.GetIndexMatTex(szEarthMaterial));
550 	if(!earth_entry)
551 		{ LogFatal(FormatString(R"(Earth material "%s" not found!)", szEarthMaterial).getData()); return false; }
552 
553 	MVehic     = Get("Vehicle");     MCVehic     = Mat2PixColDefault(MVehic);
554 	MHalfVehic = Get("HalfVehicle"); MCHalfVehic = Mat2PixColDefault(MHalfVehic);
555 	MTunnel    = Get("Tunnel");
556 	MWater     = Get("Water");
557 	MEarth     = Get(earth_entry->GetMaterialName());
558 
559 	if ((MVehic==MNone) || (MTunnel==MNone))
560 		{ LogFatal(LoadResStr("IDS_PRC_NOSYSMATS")); return false; }
561 
562 	return true;
563 }
564 
565 
SetMatReaction(int32_t iPXSMat,int32_t iLSMat,C4MaterialReaction * pReact)566 void C4MaterialMap::SetMatReaction(int32_t iPXSMat, int32_t iLSMat, C4MaterialReaction *pReact)
567 {
568 	// evaluate reaction swap
569 	if (pReact && pReact->fReverse) std::swap(iPXSMat, iLSMat);
570 	// set it
571 	ppReactionMap[(iLSMat+1)*(Num+1) + iPXSMat+1] = pReact;
572 }
573 
SaveEnumeration(C4Group & hGroup)574 bool C4MaterialMap::SaveEnumeration(C4Group &hGroup)
575 {
576 	char *mapbuf = new char [1000];
577 	mapbuf[0]=0;
578 	SAppend("[Enumeration]",mapbuf); SAppend(LineFeed,mapbuf);
579 	for (int32_t cnt=0; cnt<Num; cnt++)
580 	{
581 		SAppend(Map[cnt].Name,mapbuf);
582 		SAppend(LineFeed,mapbuf);
583 	}
584 	return hGroup.Add(C4CFN_MatMap,mapbuf,SLen(mapbuf),false,true);
585 }
586 
LoadEnumeration(C4Group & hGroup)587 bool C4MaterialMap::LoadEnumeration(C4Group &hGroup)
588 {
589 	// Load enumeration map (from savegame), succeed if not present
590 	StdStrBuf mapbuf;
591 	if (!hGroup.LoadEntryString(C4CFN_MatMap, &mapbuf)) return true;
592 
593 	// Sort material array by enumeration map, fail if some missing
594 	const char *csearch;
595 	char cmatname[C4M_MaxName+1];
596 	int32_t cmat=0;
597 	if (!(csearch = SSearch(mapbuf.getData(),"[Enumeration]"))) { return false; }
598 	csearch=SAdvanceSpace(csearch);
599 	while (IsIdentifier(*csearch))
600 	{
601 		SCopyIdentifier(csearch,cmatname,C4M_MaxName);
602 		if (!SortEnumeration(cmat,cmatname))
603 		{
604 			// Output error message!
605 			return false;
606 		}
607 		cmat++;
608 		csearch+=SLen(cmatname);
609 		csearch=SAdvanceSpace(csearch);
610 	}
611 
612 	return true;
613 }
614 
SortEnumeration(int32_t iMat,const char * szMatName)615 bool C4MaterialMap::SortEnumeration(int32_t iMat, const char *szMatName)
616 {
617 
618 	// Not enough materials loaded
619 	if (iMat>=Num) return false;
620 
621 	// Find requested mat
622 	int32_t cmat;
623 	for (cmat=iMat; cmat<Num; cmat++)
624 		if (SEqual(szMatName,Map[cmat].Name))
625 			break;
626 	// Not found
627 	if (cmat>=Num) return false;
628 
629 	// already the same?
630 	if (cmat == iMat) return true;
631 
632 	// Move requested mat to indexed position
633 	C4Material mswap;
634 	mswap = Map[iMat];
635 	Map[iMat] = Map[cmat];
636 	Map[cmat] = mswap;
637 
638 	return true;
639 }
640 
Default()641 void C4MaterialMap::Default()
642 {
643 	Num=0;
644 	Map=nullptr;
645 	ppReactionMap=nullptr;
646 	max_shape_width=max_shape_height=0;
647 }
648 
GetReaction(int32_t iPXSMat,int32_t iLandscapeMat)649 C4MaterialReaction *C4MaterialMap::GetReaction(int32_t iPXSMat, int32_t iLandscapeMat)
650 {
651 	// safety
652 	if (!ppReactionMap) return nullptr;
653 	if (!Inside<int32_t>(iPXSMat, -1, Num-1)) return nullptr;
654 	if (!Inside<int32_t>(iLandscapeMat, -1, Num-1)) return nullptr;
655 	// values OK; get func!
656 	return GetReactionUnsafe(iPXSMat, iLandscapeMat);
657 }
658 
Smoke(int32_t tx,int32_t ty,int32_t level)659 static void Smoke(int32_t tx, int32_t ty, int32_t level)
660 {
661 	// Use scripted function (global func Smoke) to create smoke
662 	// Caution: This makes engine internal smoking a synced call.
663 	C4AulParSet pars(tx, ty, level);
664 	::ScriptEngine.GetPropList()->Call(P_Smoke, &pars);
665 }
666 
mrfInsertCheck(int32_t & iX,int32_t & iY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,bool * pfPosChanged,bool no_slide=false)667 bool mrfInsertCheck(int32_t &iX, int32_t &iY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, bool *pfPosChanged, bool no_slide = false)
668 {
669 	// always manipulating pos/speed here
670 	if (pfPosChanged) *pfPosChanged = true;
671 
672 	// Move up by up to 3px to account for moving SolidMasks, other material insertions, etc.
673 	int32_t mdens = std::min(::MaterialMap.Map[iPxsMat].Density, C4M_Solid);
674 	int32_t max_upwards = 3;
675 	bool was_pushed_upwards = false;
676 	while (max_upwards-- && (::Landscape.GetDensity(iX, iY) >= mdens))
677 	{
678 		--iY;
679 		was_pushed_upwards = true;
680 	}
681 
682 	// Rough contact? May splash
683 	if (fYDir > itofix(1))
684 		if (::MaterialMap.Map[iPxsMat].SplashRate && !Random(::MaterialMap.Map[iPxsMat].SplashRate))
685 		{
686 			fYDir = -fYDir/8;
687 			fXDir = fXDir/8 + C4REAL100(Random(200) - 100);
688 			if (fYDir) return false;
689 		}
690 
691 	// Contact: Stop
692 	fYDir = -GravAccel;
693 
694 	// Incendiary mats smoke on contact even before doing their slide
695 	if (::MaterialMap.Map[iPxsMat].Incendiary)
696 		if (!Random(25))
697 		{
698 			Smoke(iX, iY, 4 + Random(3));
699 		}
700 
701 	// Move by mat path/slide
702 	int32_t iSlideX = iX, iSlideY = iY;
703 
704 	if (!no_slide && ::Landscape.FindMatSlide(iSlideX,iSlideY,Sign(GravAccel),mdens,::MaterialMap.Map[iPxsMat].MaxSlide))
705 	{
706 		// Sliding on equal material: Move directly to optimize insertion of rain onto lakes
707 		// Also move directly when shifted upwards to ensure movement on permamently moving SolidMask
708 		if (iPxsMat == iLsMat || was_pushed_upwards)
709 		{
710 			iX = iSlideX;
711 			iY = iSlideY;
712 			fXDir = 0;
713 			if (was_pushed_upwards)
714 			{
715 				// When pushed upwards and slide was found into a target position, insert directly to allow additional PXS at same location to solidify in next position in same frame
716 				if (::Landscape.GetDensity(iX, iY + Sign(GravAccel)) >= mdens)
717 				{
718 					return true;
719 				}
720 			}
721 			// Continue existing (and fall down next frame)
722 			return false;
723 		}
724 		// Otherwise, just move using xdir/ydir for nice visuals when rain is moving over landscape
725 		// Accelerate into the direction
726 		fXDir = (fXDir * 10 + Sign(iSlideX - iX)) / 11 + C4REAL10(Random(5)-2);
727 		// Slide target in range? Move there directly.
728 		if (Abs(iX - iSlideX) <= Abs(fixtoi(fXDir)))
729 		{
730 			iX = iSlideX;
731 			iY = iSlideY;
732 			if (fYDir <= 0) fXDir = 0;
733 		}
734 		// Continue existance
735 		return false;
736 	}
737 	// insertion OK
738 	return true;
739 }
740 
mrfUserCheck(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)741 bool mrfUserCheck(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
742 {
743 	// check execution mask
744 	if ((1<<evEvent) & ~pReaction->iExecMask) return false;
745 	// do splash/slide check, if desired
746 	if (pReaction->fInsertionCheck && evEvent == meePXSMove)
747 		if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
748 			return false;
749 	// checks OK; reaction may be applied
750 	return true;
751 }
752 
mrfConvert(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)753 bool C4MaterialMap::mrfConvert(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
754 {
755 	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
756 	switch (evEvent)
757 	{
758 	case meePXSMove: // PXS movement
759 		// for hardcoded stuff: only InMatConvert is Snow in Water, which does not have any collision proc
760 		if (!pReaction->fUserDefined) break;
761 		// user-defined conversions may also convert upon hitting materials
762 
763 	case meePXSPos: // PXS check before movement
764 	{
765 		// Check depth
766 		int32_t iDepth = pReaction->fUserDefined ? pReaction->iDepth : ::MaterialMap.Map[iPxsMat].InMatConvertDepth;
767 		if (!iDepth || GBackMat(iX, iY - iDepth) == iLsMat)
768 		{
769 			// Convert
770 			iPxsMat = pReaction->fUserDefined ? pReaction->iConvertMat : ::MaterialMap.Map[iPxsMat].InMatConvertTo;
771 			if (!MatValid(iPxsMat))
772 				// Convert failure (target mat not be loaded, or target may be C4TLS_MatSky): Kill Pix
773 				return true;
774 			// stop movement after conversion
775 			fXDir = fYDir = 0;
776 			if (pfPosChanged) *pfPosChanged = true;
777 		}
778 	}
779 	break;
780 
781 	case meeMassMove: // MassMover-movement
782 		// Conversion-transfer to PXS
783 		::PXS.Create(iPxsMat,itofix(iX),itofix(iY));
784 		return true;
785 	}
786 	// not handled
787 	return false;
788 }
789 
mrfPoof(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)790 bool C4MaterialMap::mrfPoof(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
791 {
792 	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
793 	switch (evEvent)
794 	{
795 	case meeMassMove: // MassMover-movement
796 	case meePXSPos: // PXS check before movement: Kill both landscape and PXS mat
797 		::Landscape.ExtractMaterial(iLSPosX,iLSPosY,false);
798 		if (!Random(3)) Smoke(iX,iY,3);
799 		if (!Random(3)) StartSoundEffectAt("Liquids::Pshshsh", iX, iY);
800 		return true;
801 
802 	case meePXSMove: // PXS movement
803 		// incindiary/extinguisher/corrosives are always same density proc; so do insertion check first
804 		// Do not allow sliding though (e.g. water on lava).
805 		if (!pReaction->fUserDefined)
806 			if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged, true))
807 				// either splash or slide prevented interaction
808 				return false;
809 		// Always kill both landscape and PXS mat
810 		::Landscape.ExtractMaterial(iLSPosX,iLSPosY,false);
811 		if (!Random(3)) Smoke(iX,iY,3);
812 		if (!Random(3)) StartSoundEffectAt("Liquids::Pshshsh", iX, iY);
813 		return true;
814 	}
815 	// not handled
816 	return false;
817 }
818 
mrfCorrode(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)819 bool C4MaterialMap::mrfCorrode(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
820 {
821 	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
822 	switch (evEvent)
823 	{
824 	case meePXSPos: // PXS check before movement
825 		// No corrosion - it would make acid incredibly effective
826 		break;
827 	case meeMassMove: // MassMover-movement
828 	{
829 		// evaluate corrosion percentage
830 		bool fDoCorrode; int d100 = Random(100);
831 		if (pReaction->fUserDefined)
832 			fDoCorrode = (d100 < pReaction->iCorrosionRate);
833 		else
834 			fDoCorrode = (d100 < ::MaterialMap.Map[iPxsMat].Corrosive) && (d100 < ::MaterialMap.Map[iLsMat].Corrode);
835 		if (fDoCorrode)
836 		{
837 			::Landscape.ClearPix(iLSPosX,iLSPosY);
838 			//::Landscape.CheckInstabilityRange(iLSPosX,iLSPosY); - more correct, but makes acid too effective as well
839 			if (!Random(5))
840 			{
841 				Smoke(iX, iY, 3 + Random(3));
842 			}
843 			if (!Random(20)) StartSoundEffectAt("Liquids::Corrode", iX, iY);
844 			return true;
845 		}
846 	}
847 	break;
848 
849 	case meePXSMove: // PXS movement
850 	{
851 		// corrodes to corrosives are always same density proc; so do insertion check first
852 		if (!pReaction->fUserDefined)
853 			if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
854 				// either splash or slide prevented interaction
855 				return false;
856 		// evaluate corrosion percentage
857 		bool fDoCorrode; int d100 = Random(100);
858 		if (pReaction->fUserDefined)
859 			fDoCorrode = (d100 < pReaction->iCorrosionRate);
860 		else
861 			fDoCorrode = (d100 < ::MaterialMap.Map[iPxsMat].Corrosive) && (d100 < ::MaterialMap.Map[iLsMat].Corrode);
862 		if (fDoCorrode)
863 		{
864 			::Landscape.ClearPix(iLSPosX,iLSPosY);
865 			::Landscape.CheckInstabilityRange(iLSPosX,iLSPosY);
866 			if (!Random(5))
867 			{
868 				Smoke(iX,iY,3+Random(3));
869 			}
870 			if (!Random(20)) StartSoundEffectAt("Liquids::Corrode", iX, iY);
871 			return true;
872 		}
873 		// Else: dead. Insert material here
874 		::Landscape.InsertMaterial(iPxsMat,&iX,&iY);
875 		return true;
876 	}
877 	}
878 	// not handled
879 	return false;
880 }
881 
mrfIncinerate(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)882 bool C4MaterialMap::mrfIncinerate(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
883 {
884 	// not available as user reaction
885 	assert(!pReaction->fUserDefined);
886 	switch (evEvent)
887 	{
888 	case meeMassMove: // MassMover-movement
889 	case meePXSPos: // PXS check before movement
890 		if (::Landscape.Incinerate(iX, iY, NO_OWNER)) return true;
891 		break;
892 
893 	case meePXSMove: // PXS movement
894 		// incinerate to inflammables are always same density proc; so do insertion check first
895 		if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
896 			// either splash or slide prevented interaction
897 			return false;
898 		// evaluate inflammation (should always succeed)
899 		if (::Landscape.Incinerate(iX, iY, NO_OWNER)) return true;
900 		// Else: dead. Insert material here
901 		::Landscape.InsertMaterial(iPxsMat,&iX,&iY);
902 		return true;
903 	}
904 	// not handled
905 	return false;
906 }
907 
mrfInsert(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)908 bool C4MaterialMap::mrfInsert(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
909 {
910 	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
911 	switch (evEvent)
912 	{
913 	case meePXSPos: // PXS check before movement
914 		break;
915 
916 	case meePXSMove: // PXS movement
917 	{
918 		// check for bounce/slide
919 		if (!pReaction->fUserDefined)
920 			if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
921 				// continue existing
922 				return false;
923 		// Else: dead. Insert material here
924 		::Landscape.InsertMaterial(iPxsMat,&iX,&iY);
925 		return true;
926 	}
927 
928 	case meeMassMove: // MassMover-movement
929 		break;
930 	}
931 	// not handled
932 	return false;
933 }
934 
mrfScript(C4MaterialReaction * pReaction,int32_t & iX,int32_t & iY,int32_t iLSPosX,int32_t iLSPosY,C4Real & fXDir,C4Real & fYDir,int32_t & iPxsMat,int32_t iLsMat,MaterialInteractionEvent evEvent,bool * pfPosChanged)935 bool C4MaterialMap::mrfScript(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, C4Real &fXDir, C4Real &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
936 {
937 	// do generic checks for user-defined reactions
938 	if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged))
939 		return false;
940 
941 	// check script func
942 	if (!pReaction->pScriptFunc) return false;
943 	// OK - let's call it!
944 	//                      0           1           2                3                        4                           5                      6               7              8
945 	int32_t iXDir1, iYDir1, iXDir2, iYDir2;
946 	C4AulParSet pars(iX, iY, iLSPosX, iLSPosY, iXDir1 = fixtoi(fXDir, 100), iYDir1 = fixtoi(fYDir, 100), iPxsMat, iLsMat, int(evEvent));
947 	if (!!pReaction->pScriptFunc->Exec(nullptr, &pars, false))
948 	{
949 		// PXS shall be killed!
950 		return true;
951 	}
952 	// PXS shall exist further: write back parameters
953 	iPxsMat = pars[6].getInt();
954 	int32_t iX2 = pars[0].getInt(), iY2 = pars[1].getInt();
955 	iXDir2 = pars[4].getInt(); iYDir2 = pars[5].getInt();
956 	if (iX!=iX2 || iY!=iY2 || iXDir1!=iXDir2 || iYDir1!=iYDir2)
957 	{
958 		// changes to pos/speed detected
959 		if (pfPosChanged) *pfPosChanged = true;
960 		iX=iX2; iY=iY2;
961 		fXDir = C4REAL100(iXDir2);
962 		fYDir = C4REAL100(iYDir2);
963 	}
964 	// OK; done
965 	return false;
966 }
967 
UpdateScriptPointers()968 void C4MaterialMap::UpdateScriptPointers()
969 {
970 	// update in all materials
971 	for (int32_t i=0; i<Num; ++i) Map[i].UpdateScriptPointers();
972 }
973 
974 C4MaterialMap MaterialMap;
975