PageRenderTime 263ms CodeModel.GetById 17ms app.highlight 237ms RepoModel.GetById 0ms app.codeStats 1ms

/src/game/landscape/C4Material.cpp

https://bitbucket.org/randrian/openclonk2
C++ | 890 lines | 674 code | 83 blank | 133 comment | 222 complexity | 3d55cb5e28e579a7e4f85baf3e6246a9 MD5 | raw file
Possible License(s): WTFPL, 0BSD, LGPL-2.1, CC-BY-3.0
  1/*
  2 * OpenClonk, http://www.openclonk.org
  3 *
  4 * Copyright (c) 1998-2000, 2007  Matthes Bender
  5 * Copyright (c) 2002, 2005-2007  Sven Eberhardt
  6 * Copyright (c) 2006-2007  Peter Wortmann
  7 * Copyright (c) 2006-2007  G?nther Brammer
  8 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
  9 *
 10 * Portions might be copyrighted by other authors who have contributed
 11 * to OpenClonk.
 12 *
 13 * Permission to use, copy, modify, and/or distribute this software for any
 14 * purpose with or without fee is hereby granted, provided that the above
 15 * copyright notice and this permission notice appear in all copies.
 16 * See isc_license.txt for full license and disclaimer.
 17 *
 18 * "Clonk" is a registered trademark of Matthes Bender.
 19 * See clonk_trademark_license.txt for full license.
 20 */
 21
 22/* Material definitions used by the landscape */
 23
 24#include <C4Include.h>
 25#include <C4Material.h>
 26#include <C4Components.h>
 27
 28#ifndef BIG_C4INCLUDE
 29
 30#include <C4Group.h>
 31#include <C4PXS.h>
 32#include <C4Random.h>
 33#include <C4ToolsDlg.h> // For C4TLS_MatSky...
 34#include <C4Texture.h>
 35#include <C4Aul.h>
 36#include <C4Landscape.h>
 37#include <C4SoundSystem.h>
 38#include <C4Effects.h>
 39#include <C4Game.h>
 40#include <C4Log.h>
 41#include <C4Physics.h> // For GravAccel
 42
 43#endif
 44
 45
 46int32_t MVehic=MNone,MTunnel=MNone,MWater=MNone,MSnow=MNone,MEarth=MNone,MGranite=MNone,MFlyAshes=MNone;
 47BYTE MCVehic=0;
 48
 49// -------------------------------------- C4MaterialReaction
 50
 51
 52struct ReactionFuncMapEntry { const char *szRFName; C4MaterialReactionFunc pFunc; };
 53
 54const ReactionFuncMapEntry ReactionFuncMap[] = {
 55	{ "Script",  &C4MaterialMap::mrfScript },
 56	{ "Convert", &C4MaterialMap::mrfConvert},
 57	{ "Poof",    &C4MaterialMap::mrfPoof },
 58	{ "Corrode", &C4MaterialMap::mrfCorrode },
 59	{ "Insert",  &C4MaterialMap::mrfInsert },
 60	{ NULL, &C4MaterialReaction::NoReaction } };
 61
 62
 63void C4MaterialReaction::CompileFunc(StdCompiler *pComp)
 64	{
 65	if (pComp->isCompiler()) pScriptFunc = NULL;
 66	// compile reaction func ptr
 67	StdStrBuf sReactionFuncName;
 68	int32_t i=0; while (ReactionFuncMap[i].szRFName && (ReactionFuncMap[i].pFunc != pFunc)) ++i;
 69	sReactionFuncName = ReactionFuncMap[i].szRFName;
 70	pComp->Value(mkNamingAdapt(sReactionFuncName,		"Type",              				StdStrBuf()	    ));
 71	i=0; while (ReactionFuncMap[i].szRFName && !SEqual(ReactionFuncMap[i].szRFName, sReactionFuncName.getData())) ++i;
 72	pFunc = ReactionFuncMap[i].pFunc;
 73	// compile the rest
 74	pComp->Value(mkNamingAdapt(TargetSpec,					"TargetSpec",								StdCopyStrBuf()	));
 75	pComp->Value(mkNamingAdapt(ScriptFunc,					"ScriptFunc",								StdCopyStrBuf()	));
 76	pComp->Value(mkNamingAdapt(iExecMask,           "ExecMask",                 ~0u             ));
 77	pComp->Value(mkNamingAdapt(fReverse,            "Reverse",                  false           ));
 78	pComp->Value(mkNamingAdapt(fInverseSpec,        "InverseSpec",              false           ));
 79	pComp->Value(mkNamingAdapt(fInsertionCheck,     "CheckSlide",               true            ));
 80	pComp->Value(mkNamingAdapt(iDepth,              "Depth",                    0               ));
 81	pComp->Value(mkNamingAdapt(sConvertMat,         "ConvertMat",               StdCopyStrBuf() ));
 82	pComp->Value(mkNamingAdapt(iCorrosionRate,      "CorrosionRate",            100             ));
 83	}
 84
 85
 86void C4MaterialReaction::ResolveScriptFuncs(const char *szMatName)
 87	{
 88	// get script func for script-defined behaviour
 89	if (pFunc == &C4MaterialMap::mrfScript)
 90		pScriptFunc = ::ScriptEngine.GetSFuncWarn(this->ScriptFunc.getData(), AA_PROTECTED, FormatString("Material reaction of \"%s\"", szMatName).getData());
 91	else
 92		pScriptFunc = NULL;
 93	}
 94
 95// -------------------------------------- C4MaterialCore
 96
 97C4MaterialCore::C4MaterialCore()
 98	{
 99	Clear();
100	}
101
102void C4MaterialCore::Clear()
103	{
104	CustomReactionList.clear();
105	sTextureOverlay.Clear();
106	sPXSGfx.Clear();
107	sBlastShiftTo.Clear();
108	sInMatConvert.Clear();
109	sInMatConvertTo.Clear();
110	sBelowTempConvertTo.Clear();
111	sAboveTempConvertTo.Clear();
112	*Name='\0';
113	MapChunkType = 0;
114	Density = 0;
115	Friction = 0;
116	DigFree = 0;
117	BlastFree = 0;
118	Dig2Object = 0;
119	Dig2ObjectRatio = 0;
120	Dig2ObjectOnRequestOnly = 0;
121	Blast2Object = 0;
122	Blast2ObjectRatio = 0;
123	Blast2PXSRatio = 0;
124	Instable = 0;
125	MaxAirSpeed = 0;
126	MaxSlide = 0;
127	WindDrift = 0;
128	Inflammable = 0;
129	Incindiary = 0;
130	Extinguisher = 0;
131	Corrosive = 0;
132	Corrode = 0;
133	Soil = 0;
134	Placement = 0;
135	OverlayType = 0;
136	PXSGfxRt.Default();
137	PXSGfxSize = 0;
138	InMatConvertDepth = 0;
139	BelowTempConvert = 0;
140	BelowTempConvertDir = 0;
141	AboveTempConvert = 0;
142	AboveTempConvertDir = 0;
143	ColorAnimation = 0;
144	TempConvStrength = 0;
145	MinHeightCount = 0;
146  SplashRate=10;
147	}
148
149void C4MaterialCore::Default()
150	{
151	Clear();
152	}
153
154bool C4MaterialCore::Load(C4Group &hGroup,
155													const char *szEntryName)
156	{
157	StdStrBuf Source;
158	if (!hGroup.LoadEntryString(szEntryName,Source))
159		return false;
160	StdStrBuf Name = hGroup.GetFullName() + DirSep + szEntryName;
161  if(!CompileFromBuf_LogWarn<StdCompilerINIRead>(*this, Source, Name.getData()))
162		return false;
163	// adjust placement, if not specified
164	if (!Placement)
165		{
166		if (DensitySolid(Density))
167			{
168			Placement=30;
169			if (!DigFree) Placement+=20;
170			if (!BlastFree) Placement+=10;
171			if (!Dig2ObjectOnRequestOnly) Placement+=10;
172			}
173		else if (DensityLiquid(Density))
174			Placement=10;
175		else Placement=5;
176		}
177	return true;
178	}
179
180void C4MaterialCore::CompileFunc(StdCompiler *pComp)
181	{
182	if (pComp->isCompiler()) Clear();
183	pComp->Name("Material");
184	pComp->Value(mkNamingAdapt(toC4CStr(Name),					"Name",								""								));
185	pComp->Value(mkNamingAdapt(ColorAnimation,					"ColorAnimation",			0									));
186	pComp->Value(mkNamingAdapt(MapChunkType,						"Shape",							0									));
187	pComp->Value(mkNamingAdapt(Density,									"Density",						0									));
188	pComp->Value(mkNamingAdapt(Friction,								"Friction",						0									));
189	pComp->Value(mkNamingAdapt(DigFree,									"DigFree",						0									));
190	pComp->Value(mkNamingAdapt(BlastFree,								"BlastFree",					0									));
191	pComp->Value(mkNamingAdapt(mkC4IDAdapt(Blast2Object),"Blast2Object",			0									));
192	pComp->Value(mkNamingAdapt(mkC4IDAdapt(Dig2Object),	"Dig2Object",					0									));
193	pComp->Value(mkNamingAdapt(Dig2ObjectRatio,					"Dig2ObjectRatio",		0									));
194	pComp->Value(mkNamingAdapt(Dig2ObjectOnRequestOnly,	"Dig2ObjectRequest",	0									));
195	pComp->Value(mkNamingAdapt(Blast2ObjectRatio,				"Blast2ObjectRatio",	0									));
196	pComp->Value(mkNamingAdapt(Blast2PXSRatio,					"Blast2PXSRatio",			0									));
197	pComp->Value(mkNamingAdapt(Instable,								"Instable",						0									));
198	pComp->Value(mkNamingAdapt(MaxAirSpeed,							"MaxAirSpeed",				0									));
199	pComp->Value(mkNamingAdapt(MaxSlide,								"MaxSlide",						0									));
200	pComp->Value(mkNamingAdapt(WindDrift,								"WindDrift",					0									));
201	pComp->Value(mkNamingAdapt(Inflammable,							"Inflammable",				0									));
202	pComp->Value(mkNamingAdapt(Incindiary,							"Incindiary",					0									));
203	pComp->Value(mkNamingAdapt(Corrode,									"Corrode",						0									));
204	pComp->Value(mkNamingAdapt(Corrosive,								"Corrosive",					0									));
205	pComp->Value(mkNamingAdapt(Extinguisher,						"Extinguisher",				0									));
206	pComp->Value(mkNamingAdapt(Soil,										"Soil",								0									));
207	pComp->Value(mkNamingAdapt(Placement,								"Placement",					0									));
208	pComp->Value(mkNamingAdapt(mkParAdapt(sTextureOverlay, StdCompiler::RCT_IdtfAllowEmpty),"TextureOverlay",			""								));
209	pComp->Value(mkNamingAdapt(OverlayType,							"OverlayType",				0									));
210	pComp->Value(mkNamingAdapt(mkParAdapt(sPXSGfx, StdCompiler::RCT_IdtfAllowEmpty),				"PXSGfx",							""								));
211	pComp->Value(mkNamingAdapt(PXSGfxRt,								"PXSGfxRt",						TargetRect0				));
212	pComp->Value(mkNamingAdapt(PXSGfxSize,							"PXSGfxSize",					PXSGfxRt.Wdt			));
213	pComp->Value(mkNamingAdapt(TempConvStrength,				"TempConvStrength",		0									));
214	pComp->Value(mkNamingAdapt(mkParAdapt(sBlastShiftTo, StdCompiler::RCT_IdtfAllowEmpty),"BlastShiftTo",				""								));
215	pComp->Value(mkNamingAdapt(mkParAdapt(sInMatConvert, StdCompiler::RCT_IdtfAllowEmpty),"InMatConvert",				""								));
216	pComp->Value(mkNamingAdapt(mkParAdapt(sInMatConvertTo, StdCompiler::RCT_IdtfAllowEmpty),"InMatConvertTo",		""								));
217	pComp->Value(mkNamingAdapt(InMatConvertDepth,				"InMatConvertDepth",	0									));
218	pComp->Value(mkNamingAdapt(AboveTempConvert,				"AboveTempConvert",		0									));
219	pComp->Value(mkNamingAdapt(AboveTempConvertDir,			"AboveTempConvertDir",0									));
220	pComp->Value(mkNamingAdapt(mkParAdapt(sAboveTempConvertTo, StdCompiler::RCT_IdtfAllowEmpty),"AboveTempConvertTo", ""					));
221	pComp->Value(mkNamingAdapt(BelowTempConvert,				"BelowTempConvert",		0									));
222	pComp->Value(mkNamingAdapt(BelowTempConvertDir,			"BelowTempConvertDir",0									));
223	pComp->Value(mkNamingAdapt(mkParAdapt(sBelowTempConvertTo, StdCompiler::RCT_IdtfAllowEmpty),"BelowTempConvertTo", ""					));
224	pComp->Value(mkNamingAdapt(MinHeightCount,				   "MinHeightCount",	  0									));
225  pComp->Value(mkNamingAdapt(SplashRate,	    			   "SplashRate",	      10								));
226	pComp->NameEnd();
227	// material reactions
228	pComp->Value(mkNamingAdapt(
229		mkSTLContainerAdapt(CustomReactionList),
230			"Reaction", std::vector<C4MaterialReaction>()));
231	}
232
233
234// -------------------------------------- C4Material
235
236C4Material::C4Material()
237  {
238  BlastShiftTo=0;
239  InMatConvertTo=MNone;
240  BelowTempConvertTo=0;
241  AboveTempConvertTo=0;
242  }
243
244void C4Material::UpdateScriptPointers()
245	{
246	for (uint32_t i = 0; i < CustomReactionList.size(); ++i)
247		CustomReactionList[i].ResolveScriptFuncs(Name);
248	}
249
250
251// -------------------------------------- C4MaterialMap
252
253
254C4MaterialMap::C4MaterialMap() : DefReactConvert(&mrfConvert), DefReactPoof(&mrfPoof), DefReactCorrode(&mrfCorrode), DefReactIncinerate(&mrfIncinerate), DefReactInsert(&mrfInsert)
255  {
256	Default();
257  }
258
259
260C4MaterialMap::~C4MaterialMap()
261  {
262  Clear();
263  }
264
265void C4MaterialMap::Clear()
266  {
267  if (Map) delete [] Map; Map=NULL;
268	delete [] ppReactionMap; ppReactionMap = NULL;
269  }
270
271int32_t C4MaterialMap::Load(C4Group &hGroup, C4Group* OverloadFile)
272  {
273	char entryname[256+1];
274
275  // Determine number of materials in files
276  int32_t mat_num=hGroup.EntryCount(C4CFN_MaterialFiles);
277
278  // Allocate new map
279	C4Material *pNewMap = new C4Material [mat_num + Num];
280	if(!pNewMap) return 0;
281
282  // Load material cores to map
283  hGroup.ResetSearch(); int32_t cnt=0;
284	while (hGroup.FindNextEntry(C4CFN_MaterialFiles,entryname))
285		{
286		// Load mat
287		if (!pNewMap[cnt].Load(hGroup,entryname))
288			{ delete [] pNewMap; return 0; }
289		// A new material?
290		if(Get(pNewMap[cnt].Name) == MNone)
291			cnt++;
292		}
293
294	// Take over old materials.
295	for(int32_t i = 0; i < Num; i++)
296		{
297		pNewMap[cnt+i] = Map[i];
298		}
299	delete [] Map;
300	Map = pNewMap;
301
302	// set material number
303	Num+=cnt;
304
305  return cnt;
306  }
307
308bool C4MaterialMap::HasMaterials(C4Group &hGroup) const
309	{
310	return !!hGroup.EntryCount(C4CFN_MaterialFiles);
311	}
312
313int32_t C4MaterialMap::Get(const char *szMaterial)
314  {
315  int32_t cnt;
316  for (cnt=0; cnt<Num; cnt++)
317    if (SEqualNoCase(szMaterial,Map[cnt].Name))
318      return cnt;
319  return MNone;
320  }
321
322
323bool C4MaterialMap::CrossMapMaterials() // Called after load
324	{
325	// Check material number
326	if (::MaterialMap.Num>C4MaxMaterial)
327		{ LogFatal(LoadResStr("IDS_PRC_TOOMANYMATS")); return false; }
328	// build reaction function map
329	delete [] ppReactionMap;
330	typedef C4MaterialReaction * C4MaterialReactionPtr;
331	ppReactionMap = new C4MaterialReactionPtr[(Num+1)*(Num+1)];
332	for (int32_t iMatPXS=-1; iMatPXS<Num; iMatPXS++)
333		{
334		C4Material *pMatPXS = (iMatPXS+1) ? Map+iMatPXS : NULL;
335		for (int32_t iMatLS=-1; iMatLS<Num; iMatLS++)
336			{
337			C4MaterialReaction *pReaction = NULL;
338			C4Material *pMatLS  = ( iMatLS+1) ? Map+ iMatLS : NULL;
339			// natural stuff: material conversion here?
340			if (pMatPXS && pMatPXS->sInMatConvert.getLength() && SEqualNoCase(pMatPXS->sInMatConvert.getData(), pMatLS ? pMatLS->Name : C4TLS_MatSky))
341				pReaction = &DefReactConvert;
342			// the rest is happening for same/higher densities only
343			else if ((MatDensity(iMatPXS) <= MatDensity(iMatLS)) && pMatPXS && pMatLS)
344				{
345				// incindiary vs extinguisher
346				if ((pMatPXS->Incindiary && pMatLS->Extinguisher) || (pMatPXS->Extinguisher && pMatLS->Incindiary))
347					pReaction = &DefReactPoof;
348				// incindiary vs inflammable
349				else if ((pMatPXS->Incindiary && pMatLS->Inflammable) || (pMatPXS->Inflammable && pMatLS->Incindiary))
350					pReaction = &DefReactIncinerate;
351				// corrosive vs corrode
352				else if (pMatPXS->Corrosive && pMatLS->Corrode)
353					pReaction = &DefReactCorrode;
354				// otherwise, when hitting same or higher density: Material insertion
355				else
356					pReaction = &DefReactInsert;
357				}
358			// assign the function; or NULL for no reaction
359			SetMatReaction(iMatPXS, iMatLS, pReaction);
360			}
361		}
362	// material-specific initialization
363	int32_t cnt;
364	for (cnt=0; cnt<Num; cnt++)
365		{
366		C4Material *pMat = Map+cnt;
367		const char *szTextureOverlay = NULL;
368		// newgfx: init pattern
369		if (Map[cnt].sTextureOverlay.getLength())
370			if (::TextureMap.GetTexture(Map[cnt].sTextureOverlay.getLength()))
371				{
372				szTextureOverlay = Map[cnt].sTextureOverlay.getData();
373				// backwards compatibility: if a pattern was specified although the no-pattern flag was set, overwrite that flag
374				if (Map[cnt].OverlayType & C4MatOv_None)
375					{
376					DebugLogF("Error in overlay of material %s: Flag C4MatOv_None ignored because a custom overlay (%s) was specified!", Map[cnt].Name, szTextureOverlay);
377					Map[cnt].OverlayType &= ~C4MatOv_None;
378					}
379				}
380		// default to smooth
381		if (!szTextureOverlay)
382			szTextureOverlay = "Smooth";
383		// search/create entry in texmap
384		Map[cnt].DefaultMatTex = ::TextureMap.GetIndex(Map[cnt].Name, szTextureOverlay, true,
385			FormatString("DefaultMatTex of mat %s", Map[cnt].Name).getData());
386		// init PXS facet
387		SURFACE sfcTexture;
388		C4Texture * Texture;
389		if (Map[cnt].sPXSGfx.getLength())
390			if (Texture=::TextureMap.GetTexture(Map[cnt].sPXSGfx.getData()))
391				if (sfcTexture=Texture->Surface32)
392					Map[cnt].PXSFace.Set(sfcTexture, Map[cnt].PXSGfxRt.x, Map[cnt].PXSGfxRt.y, Map[cnt].PXSGfxRt.Wdt, Map[cnt].PXSGfxRt.Hgt);
393		// evaluate reactions for that material
394		for (unsigned int iRCnt = 0; iRCnt < pMat->CustomReactionList.size(); ++iRCnt)
395			{
396			C4MaterialReaction *pReact = &(pMat->CustomReactionList[iRCnt]);
397			if (pReact->sConvertMat.getLength()) pReact->iConvertMat = Get(pReact->sConvertMat.getData()); else pReact->iConvertMat = -1;
398			// evaluate target spec
399			int32_t tmat;
400			if (MatValid(tmat=Get(pReact->TargetSpec.getData())))
401				{
402				// single material target
403				if (pReact->fInverseSpec)
404					for (int32_t cnt2=-1; cnt2<Num; cnt2++) if (cnt2!=tmat) SetMatReaction(cnt, cnt2, pReact);
405				else
406					SetMatReaction(cnt, tmat, pReact);
407				}
408			else if (SEqualNoCase(pReact->TargetSpec.getData(), "All"))
409				{
410				// add to all materials, including sky
411				if (!pReact->fInverseSpec) for (int32_t cnt2=-1; cnt2<Num; cnt2++) SetMatReaction(cnt, cnt2, pReact);
412				}
413			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Solid"))
414				{
415				// add to all solid materials
416				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
417				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (DensitySolid(Map[cnt2].Density) != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
418				}
419			else if (SEqualNoCase(pReact->TargetSpec.getData(), "SemiSolid"))
420				{
421				// add to all semisolid materials
422				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
423				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (DensitySemiSolid(Map[cnt2].Density) != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
424				}
425			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Background"))
426				{
427				// add to all BG materials, including sky
428				if (!pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
429				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Density != pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
430				}
431			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Sky"))
432				{
433				// add to sky
434				if (!pReact->fInverseSpec)
435					SetMatReaction(cnt, -1, pReact);
436				else
437					for (int32_t cnt2=0; cnt2<Num; cnt2++) SetMatReaction(cnt, cnt2, pReact);
438				}
439			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Incindiary"))
440				{
441				// add to all incendiary materials
442				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
443				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Incindiary == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
444				}
445			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Extinguisher"))
446				{
447				// add to all incendiary materials
448				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
449				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Extinguisher == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
450				}
451			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Inflammable"))
452				{
453				// add to all incendiary materials
454				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
455				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Inflammable == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
456				}
457			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Corrosive"))
458				{
459				// add to all incendiary materials
460				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
461				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Corrosive == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
462				}
463			else if (SEqualNoCase(pReact->TargetSpec.getData(), "Corrode"))
464				{
465				// add to all incendiary materials
466				if (pReact->fInverseSpec) SetMatReaction(cnt, -1, pReact);
467				for (int32_t cnt2=0; cnt2<Num; cnt2++) if (!Map[cnt2].Corrode == pReact->fInverseSpec) SetMatReaction(cnt, cnt2, pReact);
468				}
469			}
470		}
471	// second loop (DefaultMatTex is needed by GetIndexMatTex)
472  for (cnt=0; cnt<Num; cnt++)
473    {
474    if (Map[cnt].sBlastShiftTo.getLength())
475			Map[cnt].BlastShiftTo=::TextureMap.GetIndexMatTex(Map[cnt].sBlastShiftTo.getData(), NULL, true, FormatString("BlastShiftTo of mat %s", Map[cnt].Name).getData());
476    if (Map[cnt].sInMatConvertTo.getLength())
477      Map[cnt].InMatConvertTo=Get(Map[cnt].sInMatConvertTo.getData());
478    if (Map[cnt].sBelowTempConvertTo.getLength())
479      Map[cnt].BelowTempConvertTo=::TextureMap.GetIndexMatTex(Map[cnt].sBelowTempConvertTo.getData(), NULL, true, FormatString("BelowTempConvertTo of mat %s", Map[cnt].Name).getData());
480    if (Map[cnt].sAboveTempConvertTo.getLength())
481      Map[cnt].AboveTempConvertTo=::TextureMap.GetIndexMatTex(Map[cnt].sAboveTempConvertTo.getData(), NULL, true, FormatString("AboveTempConvertTo of mat %s", Map[cnt].Name).getData());
482		}
483#if 0
484	int32_t i=0;
485	while (ReactionFuncMap[i].szRFName) {printf("%s: %p\n", ReactionFuncMap[i].szRFName, ReactionFuncMap[i].pFunc);++i;}
486	for (int32_t cnt=-1; cnt<Num; cnt++)
487		for (int32_t cnt2=-1; cnt2<Num; cnt2++)
488			if (ppReactionMap[(cnt2+1)*(Num+1) + cnt+1])
489				printf("%s -> %s: %p\n", Map[cnt].Name, Map[cnt2].Name, ppReactionMap[(cnt2+1)*(Num+1) + cnt+1]->pFunc);
490#endif
491	// Get hardcoded system material indices
492	MVehic   = Get("Vehicle"); MCVehic = Mat2PixColDefault(MVehic);
493	MTunnel  = Get("Tunnel");
494	MWater   = Get("Water");
495	MSnow    = Get("Snow");
496	MGranite = Get("Granite");
497	MFlyAshes= Get("FlyAshes");
498	MEarth   = Get(Game.C4S.Landscape.Material);
499	if ((MVehic==MNone) || (MTunnel==MNone))
500		{ LogFatal(LoadResStr("IDS_PRC_NOSYSMATS")); return false; }
501	return true;
502	}
503
504
505void C4MaterialMap::SetMatReaction(int32_t iPXSMat, int32_t iLSMat, C4MaterialReaction *pReact)
506	{
507	// evaluate reaction swap
508	if (pReact && pReact->fReverse) Swap(iPXSMat, iLSMat);
509	// set it
510	ppReactionMap[(iLSMat+1)*(Num+1) + iPXSMat+1] = pReact;
511	}
512
513bool C4MaterialMap::SaveEnumeration(C4Group &hGroup)
514	{
515	char *mapbuf = new char [1000];
516	mapbuf[0]=0;
517	SAppend("[Enumeration]",mapbuf); SAppend(LineFeed,mapbuf);
518  for (int32_t cnt=0; cnt<Num; cnt++)
519		{
520		SAppend(Map[cnt].Name,mapbuf);
521		SAppend(LineFeed,mapbuf);
522		}
523	SAppend(EndOfFile,mapbuf);
524	return hGroup.Add(C4CFN_MatMap,mapbuf,SLen(mapbuf),false,true);
525	}
526
527bool C4MaterialMap::LoadEnumeration(C4Group &hGroup)
528	{
529	// Load enumeration map (from savegame), succeed if not present
530	StdStrBuf mapbuf;
531	if (!hGroup.LoadEntryString(C4CFN_MatMap, mapbuf)) return true;
532
533	// Sort material array by enumeration map, fail if some missing
534	const char *csearch;
535	char cmatname[C4M_MaxName+1];
536	int32_t cmat=0;
537	if (!(csearch = SSearch(mapbuf.getData(),"[Enumeration]"))) { return false; }
538	csearch=SAdvanceSpace(csearch);
539	while (IsIdentifier(*csearch))
540		{
541		SCopyIdentifier(csearch,cmatname,C4M_MaxName);
542		if (!SortEnumeration(cmat,cmatname))
543			{
544			// Output error message!
545			return false;
546			}
547		cmat++;
548		csearch+=SLen(cmatname);
549		csearch=SAdvanceSpace(csearch);
550		}
551
552	return true;
553	}
554
555bool C4MaterialMap::SortEnumeration(int32_t iMat, const char *szMatName)
556	{
557
558	// Not enough materials loaded
559	if (iMat>=Num) return false;
560
561	// Find requested mat
562	int32_t cmat;
563	for (cmat=iMat; cmat<Num; cmat++)
564		if (SEqual(szMatName,Map[cmat].Name))
565			break;
566	// Not found
567	if (cmat>=Num) return false;
568
569	// already the same?
570	if (cmat == iMat) return true;
571
572	// Move requested mat to indexed position
573	C4Material mswap;
574	mswap = Map[iMat];
575	Map[iMat] = Map[cmat];
576	Map[cmat] = mswap;
577
578	return true;
579	}
580
581void C4MaterialMap::Default()
582	{
583  Num=0;
584  Map=NULL;
585	ppReactionMap=NULL;
586	}
587
588C4MaterialReaction *C4MaterialMap::GetReaction(int32_t iPXSMat, int32_t iLandscapeMat)
589	{
590	// safety
591	if (!ppReactionMap) return NULL;
592	if (!Inside<int32_t>(iPXSMat, -1, Num-1)) return NULL;
593	if (!Inside<int32_t>(iLandscapeMat, -1, Num-1)) return NULL;
594	// values OK; get func!
595	return GetReactionUnsafe(iPXSMat, iLandscapeMat);
596	}
597
598
599bool mrfInsertCheck(int32_t &iX, int32_t &iY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, bool *pfPosChanged)
600	{
601	// always manipulating pos/speed here
602	if (pfPosChanged) *pfPosChanged = true;
603
604	// Rough contact? May splash
605	if (fYDir > itofix(1))
606		if (::MaterialMap.Map[iPxsMat].SplashRate && !Random(::MaterialMap.Map[iPxsMat].SplashRate))
607			{
608			fYDir = -fYDir/8;
609			fXDir = fXDir/8 + FIXED100(Random(200) - 100);
610			if (fYDir) return false;
611			}
612
613	// Contact: Stop
614	fYDir = 0;
615
616	// Incindiary mats smoke on contact even before doing their slide
617	if (::MaterialMap.Map[iPxsMat].Incindiary)
618		if (!Random(25)) Smoke(iX, iY, 4+Rnd3() );
619
620	// Move by mat path/slide
621	int32_t iSlideX = iX, iSlideY = iY;
622	if (::Landscape.FindMatSlide(iSlideX,iSlideY,Sign(GravAccel),::MaterialMap.Map[iPxsMat].Density,::MaterialMap.Map[iPxsMat].MaxSlide))
623		{
624		if(iPxsMat == iLsMat)
625			{ iX = iSlideX; iY = iSlideY; fXDir = 0; return false; }
626		// Accelerate into the direction
627		fXDir = (fXDir * 10 + Sign(iSlideX - iX)) / 11 + FIXED10(Random(5)-2);
628		// Slide target in range? Move there directly.
629		if(Abs(iX - iSlideX) <= Abs(fixtoi(fXDir)))
630			{
631			iX = iSlideX;
632			iY = iSlideY;
633			if(fYDir <= 0) fXDir = 0;
634			}
635		// Continue existance
636		return false;
637		}
638	// insertion OK
639	return true;
640	}
641
642bool mrfUserCheck(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
643	{
644	// check execution mask
645	if ((1<<evEvent) & ~pReaction->iExecMask) return false;
646	// do splash/slide check, if desired
647	if (pReaction->fInsertionCheck && evEvent == meePXSMove)
648		if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
649			return false;
650	// checks OK; reaction may be applied
651	return true;
652	}
653
654bool C4MaterialMap::mrfConvert(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
655	{
656	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
657	switch (evEvent)
658		{
659		case meePXSMove: // PXS movement
660			// for hardcoded stuff: only InMatConvert is Snow in Water, which does not have any collision proc
661			if (!pReaction->fUserDefined) break;
662			// user-defined conversions may also convert upon hitting materials
663
664		case meePXSPos: // PXS check before movement
665			{
666			// Check depth
667			int32_t iDepth = pReaction->fUserDefined ? pReaction->iDepth : ::MaterialMap.Map[iPxsMat].InMatConvertDepth;
668			if (!iDepth || GBackMat(iX, iY - iDepth) == iLsMat)
669				{
670				// Convert
671				iPxsMat = pReaction->fUserDefined ? pReaction->iConvertMat : ::MaterialMap.Map[iPxsMat].InMatConvertTo;
672				if (!MatValid(iPxsMat))
673					// Convert failure (target mat not be loaded, or target may be C4TLS_MatSky): Kill Pix
674					return true;
675				// stop movement after conversion
676				fXDir = fYDir = 0;
677				if (pfPosChanged) *pfPosChanged = true;
678				}
679			}
680			break;
681
682		case meeMassMove: // MassMover-movement
683		  // Conversion-transfer to PXS
684			::PXS.Create(iPxsMat,itofix(iX),itofix(iY));
685			return true;
686		}
687	// not handled
688	return false;
689	}
690
691bool C4MaterialMap::mrfPoof(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
692	{
693	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
694	switch (evEvent)
695		{
696		case meeMassMove: // MassMover-movement
697		case meePXSPos: // PXS check before movement: Kill both landscape and PXS mat
698			::Landscape.ExtractMaterial(iLSPosX,iLSPosY);
699			if (!Rnd3()) Smoke(iX,iY,3);
700			if (!Rnd3()) StartSoundEffectAt("Pshshsh", iX, iY);
701			return true;
702
703		case meePXSMove: // PXS movement
704			// incindiary/extinguisher/corrosives are always same density proc; so do insertion check first
705			if (!pReaction->fUserDefined)
706				if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
707					// either splash or slide prevented interaction
708					return false;
709			// Always kill both landscape and PXS mat
710			::Landscape.ExtractMaterial(iLSPosX,iLSPosY);
711			if (!Rnd3()) Smoke(iX,iY,3);
712			if (!Rnd3()) StartSoundEffectAt("Pshshsh", iX, iY);
713			return true;
714		}
715	// not handled
716	return false;
717	}
718
719bool C4MaterialMap::mrfCorrode(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
720	{
721	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
722	switch (evEvent)
723		{
724		case meePXSPos: // PXS check before movement
725			// No corrosion - it would make acid incredibly effective
726			break;
727		case meeMassMove: // MassMover-movement
728			{
729			// evaluate corrosion percentage
730			bool fDoCorrode;
731			if (pReaction->fUserDefined)
732				fDoCorrode = (Random(100) < pReaction->iCorrosionRate);
733			else
734				fDoCorrode = (Random(100) < ::MaterialMap.Map[iPxsMat].Corrosive) && (Random(100) < ::MaterialMap.Map[iLsMat].Corrode);
735			if (fDoCorrode)
736				{
737				ClearBackPix(iLSPosX,iLSPosY);
738				//::Landscape.CheckInstabilityRange(iLSPosX,iLSPosY); - more correct, but makes acid too effective as well
739				if (!Random(5)) Smoke(iX,iY,3+Random(3));
740				if (!Random(20)) StartSoundEffectAt("Corrode", iX, iY);
741				return true;
742				}
743			}
744			break;
745
746		case meePXSMove: // PXS movement
747			{
748			// corrodes to corrosives are always same density proc; so do insertion check first
749			if (!pReaction->fUserDefined)
750				if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
751					// either splash or slide prevented interaction
752					return false;
753			// evaluate corrosion percentage
754			bool fDoCorrode;
755			if (pReaction->fUserDefined)
756				fDoCorrode = (Random(100) < pReaction->iCorrosionRate);
757			else
758				fDoCorrode = (Random(100) < ::MaterialMap.Map[iPxsMat].Corrosive) && (Random(100) < ::MaterialMap.Map[iLsMat].Corrode);
759			if (fDoCorrode)
760				{
761				ClearBackPix(iLSPosX,iLSPosY);
762				::Landscape.CheckInstabilityRange(iLSPosX,iLSPosY);
763				if (!Random(5)) Smoke(iX,iY,3+Random(3));
764				if (!Random(20)) StartSoundEffectAt("Corrode", iX, iY);
765				return true;
766				}
767			// Else: dead. Insert material here
768			::Landscape.InsertMaterial(iPxsMat,iX,iY);
769			return true;
770			}
771		}
772	// not handled
773	return false;
774	}
775
776bool C4MaterialMap::mrfIncinerate(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
777	{
778	// not available as user reaction
779	assert(!pReaction->fUserDefined);
780	switch (evEvent)
781		{
782		case meeMassMove: // MassMover-movement
783		case meePXSPos: // PXS check before movement
784			if (::Landscape.Incinerate(iX, iY)) return true;
785			break;
786
787		case meePXSMove: // PXS movement
788			// incinerate to inflammables are always same density proc; so do insertion check first
789			if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
790				// either splash or slide prevented interaction
791				return false;
792			// evaluate inflammation (should always succeed)
793			if (::Landscape.Incinerate(iX, iY)) return true;
794			// Else: dead. Insert material here
795			::Landscape.InsertMaterial(iPxsMat,iX,iY);
796			return true;
797		}
798	// not handled
799	return false;
800	}
801
802bool C4MaterialMap::mrfInsert(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
803	{
804	if (pReaction->fUserDefined) if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged)) return false;
805	switch (evEvent)
806		{
807		case meePXSPos: // PXS check before movement
808			break;
809
810		case meePXSMove: // PXS movement
811			{
812			// check for bounce/slide
813			if (!pReaction->fUserDefined)
814				if (!mrfInsertCheck(iX, iY, fXDir, fYDir, iPxsMat, iLsMat, pfPosChanged))
815					// continue existing
816					return false;
817			// Else: dead. Insert material here
818			::Landscape.InsertMaterial(iPxsMat,iX,iY);
819			return true;
820			}
821
822		case meeMassMove: // MassMover-movement
823			break;
824		}
825	// not handled
826	return false;
827	}
828
829bool C4MaterialMap::mrfScript(C4MaterialReaction *pReaction, int32_t &iX, int32_t &iY, int32_t iLSPosX, int32_t iLSPosY, FIXED &fXDir, FIXED &fYDir, int32_t &iPxsMat, int32_t iLsMat, MaterialInteractionEvent evEvent, bool *pfPosChanged)
830	{
831	// do generic checks for user-defined reactions
832	if (!mrfUserCheck(pReaction, iX, iY, iLSPosX, iLSPosY, fXDir, fYDir, iPxsMat, iLsMat, evEvent, pfPosChanged))
833		return false;
834
835	// check script func
836	if (!pReaction->pScriptFunc) return false;
837	// OK - let's call it!
838	//                      0           1           2                3                        4                           5                      6               7              8
839	int32_t iXDir1, iYDir1, iXDir2, iYDir2;
840	C4AulParSet pars(C4VInt(iX), C4VInt(iY), C4VInt(iLSPosX), C4VInt(iLSPosY), C4VInt(iXDir1=fixtoi(fXDir, 100)), C4VInt(iYDir1=fixtoi(fYDir, 100)), C4VInt(iPxsMat), C4VInt(iLsMat), C4VInt(evEvent));
841	if (!!pReaction->pScriptFunc->Exec(NULL, &pars, false))
842		{
843		// PXS shall be killed!
844		return true;
845		}
846	// PXS shall exist further: write back parameters
847	iPxsMat = pars[6].getInt();
848	int32_t iX2 = pars[0].getInt(), iY2 = pars[1].getInt();
849	iXDir2 = pars[4].getInt(); iYDir2 = pars[5].getInt();
850	if (iX!=iX2 || iY!=iY2 || iXDir1!=iXDir2 || iYDir1!=iYDir2)
851		{
852		// changes to pos/speed detected
853		if (pfPosChanged) *pfPosChanged = true;
854		iX=iX2; iY=iY2;
855		fXDir = FIXED100(iXDir2);
856		fYDir = FIXED100(iYDir2);
857		}
858	// OK; done
859	return false;
860	}
861
862void C4MaterialMap::UpdateScriptPointers()
863	{
864	// update in all materials
865	for (int32_t i=0; i<Num; ++i) Map[i].UpdateScriptPointers();
866	}
867
868
869int32_t PixCol2MatOld(BYTE pixc)
870	{
871	const int C4M_ColsPerMat = 3;
872	if (pixc < GBM) return MNone;
873	pixc &= 63; // Substract GBM, ignore IFT
874	if (pixc > ::MaterialMap.Num*C4M_ColsPerMat-1) return MNone;
875	return pixc / C4M_ColsPerMat;
876	}
877
878int32_t PixCol2MatOld2(BYTE pixc)
879  {
880	int32_t iMat = ((int32_t) (pixc&0x7f)) -1;
881	// if above MVehic, don't forget additional vehicle-colors
882	if (iMat<=MVehic) return iMat;
883	// equals middle vehicle-color
884	if (iMat==MVehic+1) return MVehic;
885	// above: range check
886	iMat-=2; if (iMat >= ::MaterialMap.Num) return MNone;
887	return iMat;
888  }
889
890C4MaterialMap MaterialMap;