/src/game/landscape/C4Material.cpp
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;