1 #include "Simulation.h"
2 
3 #include <iostream>
4 #include <cmath>
5 #include <set>
6 #ifdef _MSC_VER
7 #include <intrin.h>
8 #else
9 #include <strings.h>
10 #endif
11 
12 #include "Air.h"
13 #include "Config.h"
14 #include "CoordStack.h"
15 #include "ElementClasses.h"
16 #include "Gravity.h"
17 #include "Sample.h"
18 #include "Snapshot.h"
19 
20 #include "Misc.h"
21 #include "ToolClasses.h"
22 #include "Config.h"
23 #include "SimulationData.h"
24 
25 #include "graphics/Renderer.h"
26 
27 #include "client/GameSave.h"
28 #include "common/tpt-compat.h"
29 #include "common/tpt-minmax.h"
30 #include "common/tpt-rand.h"
31 #include "gui/game/Brush.h"
32 
33 #ifdef LUACONSOLE
34 #include "lua/LuaScriptInterface.h"
35 #include "lua/LuaScriptHelper.h"
36 #endif
37 
38 extern int Element_PPIP_ppip_changed;
39 extern int Element_LOLZ_RuleTable[9][9];
40 extern int Element_LOLZ_lolz[XRES/9][YRES/9];
41 extern int Element_LOVE_RuleTable[9][9];
42 extern int Element_LOVE_love[XRES/9][YRES/9];
43 
Load(GameSave * save,bool includePressure)44 int Simulation::Load(GameSave * save, bool includePressure)
45 {
46 	return Load(save, includePressure, 0, 0);
47 }
48 
Load(GameSave * save,bool includePressure,int fullX,int fullY)49 int Simulation::Load(GameSave * save, bool includePressure, int fullX, int fullY)
50 {
51 	int x, y, r;
52 
53 	if (!save)
54 		return 1;
55 	try
56 	{
57 		save->Expand();
58 	}
59 	catch (ParseException &)
60 	{
61 		return 1;
62 	}
63 
64 	//Align to blockMap
65 	int blockX = (fullX + CELL/2)/CELL;
66 	int blockY = (fullY + CELL/2)/CELL;
67 	fullX = blockX*CELL;
68 	fullY = blockY*CELL;
69 	unsigned int pmapmask = (1<<save->pmapbits)-1;
70 
71 	int partMap[PT_NUM];
72 	for(int i = 0; i < PT_NUM; i++)
73 	{
74 		partMap[i] = i;
75 	}
76 	if(save->palette.size())
77 	{
78 		for(std::vector<GameSave::PaletteItem>::iterator iter = save->palette.begin(), end = save->palette.end(); iter != end; ++iter)
79 		{
80 			GameSave::PaletteItem pi = *iter;
81 			if (pi.second > 0 && pi.second < PT_NUM)
82 			{
83 				int myId = 0;
84 				for (int i = 0; i < PT_NUM; i++)
85 				{
86 					if (elements[i].Enabled && elements[i].Identifier == pi.first)
87 						myId = i;
88 				}
89 				// if this is a custom element, set the ID to the ID we found when comparing identifiers in the palette map
90 				// set type to 0 if we couldn't find an element with that identifier present when loading,
91 				//  unless this is a default element, in which case keep the current ID, because otherwise when an element is renamed it wouldn't show up anymore in older saves
92 				if (myId != 0 || !pi.first.BeginsWith("DEFAULT_PT_"))
93 					partMap[pi.second] = myId;
94 			}
95 		}
96 	}
97 
98 	int i;
99 	// Map of soap particles loaded into this save, old ID -> new ID
100 	std::map<unsigned int, unsigned int> soapList;
101 	for (int n = 0; n < NPART && n < save->particlesCount; n++)
102 	{
103 		Particle tempPart = save->particles[n];
104 		tempPart.x += (float)fullX;
105 		tempPart.y += (float)fullY;
106 		x = int(tempPart.x + 0.5f);
107 		y = int(tempPart.y + 0.5f);
108 
109 		if (tempPart.type >= 0 && tempPart.type < PT_NUM)
110 			tempPart.type = partMap[tempPart.type];
111 		else
112 			continue;
113 
114 		// Ensure we can spawn this element
115 		if ((player.spwn == 1 && tempPart.type==PT_STKM) || (player2.spwn == 1 && tempPart.type==PT_STKM2))
116 			continue;
117 		if ((tempPart.type == PT_SPAWN && elementCount[PT_SPAWN]) || (tempPart.type == PT_SPAWN2 && elementCount[PT_SPAWN2]))
118 			continue;
119 		bool Element_FIGH_CanAlloc(Simulation *sim);
120 		if (tempPart.type == PT_FIGH && !Element_FIGH_CanAlloc(this))
121 			continue;
122 		if (!elements[tempPart.type].Enabled)
123 			continue;
124 
125 		// These store type in ctype, but are special because they store extra information in the bits after type
126 		if (tempPart.type == PT_CRAY || tempPart.type == PT_DRAY || tempPart.type == PT_CONV)
127 		{
128 			int ctype = tempPart.ctype & pmapmask;
129 			int extra = tempPart.ctype >> save->pmapbits;
130 			if (ctype >= 0 && ctype < PT_NUM)
131 				ctype = partMap[ctype];
132 			tempPart.ctype = PMAP(extra, ctype);
133 		}
134 		else if (GameSave::TypeInCtype(tempPart.type, tempPart.ctype))
135 		{
136 			tempPart.ctype = partMap[tempPart.ctype];
137 		}
138 		// also stores extra bits past type (only STOR right now)
139 		if (GameSave::TypeInTmp(tempPart.type))
140 		{
141 			int tmp = tempPart.tmp & pmapmask;
142 			int extra = tempPart.tmp >> save->pmapbits;
143 			tmp = partMap[TYP(tmp)];
144 			tempPart.tmp = PMAP(extra, tmp);
145 		}
146 		if (GameSave::TypeInTmp2(tempPart.type, tempPart.tmp2))
147 		{
148 			tempPart.tmp2 = partMap[tempPart.tmp2];
149 		}
150 
151 		//Replace existing
152 		if ((r = pmap[y][x]))
153 		{
154 			elementCount[parts[ID(r)].type]--;
155 			parts[ID(r)] = tempPart;
156 			i = ID(r);
157 			elementCount[tempPart.type]++;
158 		}
159 		else if ((r = photons[y][x]))
160 		{
161 			elementCount[parts[ID(r)].type]--;
162 			parts[ID(r)] = tempPart;
163 			i = ID(r);
164 			elementCount[tempPart.type]++;
165 		}
166 		//Allocate new particle
167 		else
168 		{
169 			if (pfree == -1)
170 				break;
171 			i = pfree;
172 			pfree = parts[i].life;
173 			if (i > parts_lastActiveIndex)
174 				parts_lastActiveIndex = i;
175 			parts[i] = tempPart;
176 			elementCount[tempPart.type]++;
177 		}
178 
179 		void Element_STKM_init_legs(Simulation * sim, playerst *playerp, int i);
180 		switch (parts[i].type)
181 		{
182 		case PT_STKM:
183 			Element_STKM_init_legs(this, &player, i);
184 			player.spwn = 1;
185 			player.elem = PT_DUST;
186 
187 			if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) ||
188 			        (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR))
189 			{
190 				player.fan = true;
191 			}
192 			if (save->stkm.rocketBoots1)
193 				player.rocketBoots = true;
194 			if (save->stkm.fan1)
195 				player.fan = true;
196 			break;
197 		case PT_STKM2:
198 			Element_STKM_init_legs(this, &player2, i);
199 			player2.spwn = 1;
200 			player2.elem = PT_DUST;
201 			if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) ||
202 			        (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR))
203 			{
204 				player2.fan = true;
205 			}
206 			if (save->stkm.rocketBoots2)
207 				player2.rocketBoots = true;
208 			if (save->stkm.fan2)
209 				player2.fan = true;
210 			break;
211 		case PT_SPAWN:
212 			player.spawnID = i;
213 			break;
214 		case PT_SPAWN2:
215 			player2.spawnID = i;
216 			break;
217 		case PT_FIGH:
218 		{
219 			unsigned int oldTmp = parts[i].tmp;
220 			int Element_FIGH_Alloc(Simulation *sim);
221 			parts[i].tmp = Element_FIGH_Alloc(this);
222 			if (parts[i].tmp >= 0)
223 			{
224 				bool fan = false;
225 				if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR)
226 						|| (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR))
227 				{
228 					fan = true;
229 					parts[i].ctype = 0;
230 				}
231 				fighters[parts[i].tmp].elem = PT_DUST;
232 				void Element_FIGH_NewFighter(Simulation *sim, int fighterID, int i, int elem);
233 				Element_FIGH_NewFighter(this, parts[i].tmp, i, parts[i].ctype);
234 				if (fan)
235 					fighters[parts[i].tmp].fan = true;
236 				for (unsigned int fighNum : save->stkm.rocketBootsFigh)
237 				{
238 					if (fighNum == oldTmp)
239 						fighters[parts[i].tmp].rocketBoots = true;
240 				}
241 				for (unsigned int fighNum : save->stkm.fanFigh)
242 				{
243 					if (fighNum == oldTmp)
244 						fighters[parts[i].tmp].fan = true;
245 				}
246 			}
247 			else
248 			{
249 				// Should not be possible because we verify with CanAlloc above this
250 				parts[i].type = 0;
251 			}
252 			break;
253 		}
254 		case PT_SOAP:
255 			soapList.insert(std::pair<unsigned int, unsigned int>(n, i));
256 			break;
257 
258 		// List of elements that load pavg with a multiplicative bias of 2**6
259 		// (or not at all if pressure is not loaded).
260 		// If you change this list, change it in GameSave::serialiseOPS and GameSave::readOPS too!
261 		case PT_QRTZ:
262 		case PT_GLAS:
263 		case PT_TUNG:
264 			if (!includePressure)
265 			{
266 				parts[i].pavg[0] = 0;
267 				parts[i].pavg[1] = 0;
268 			}
269 			break;
270 		}
271 	}
272 	parts_lastActiveIndex = NPART-1;
273 	force_stacking_check = true;
274 	Element_PPIP_ppip_changed = 1;
275 	RecalcFreeParticles(false);
276 
277 	// fix SOAP links using soapList, a map of old particle ID -> new particle ID
278 	// loop through every old particle (loaded from save), and convert .tmp / .tmp2
279 	for (std::map<unsigned int, unsigned int>::iterator iter = soapList.begin(), end = soapList.end(); iter != end; ++iter)
280 	{
281 		int i = (*iter).second;
282 		if ((parts[i].ctype & 0x2) == 2)
283 		{
284 			std::map<unsigned int, unsigned int>::iterator n = soapList.find(parts[i].tmp);
285 			if (n != end)
286 				parts[i].tmp = n->second;
287 			// sometimes the proper SOAP isn't found. It should remove the link, but seems to break some saves
288 			// so just ignore it
289 		}
290 		if ((parts[i].ctype & 0x4) == 4)
291 		{
292 			std::map<unsigned int, unsigned int>::iterator n = soapList.find(parts[i].tmp2);
293 			if (n != end)
294 				parts[i].tmp2 = n->second;
295 			// sometimes the proper SOAP isn't found. It should remove the link, but seems to break some saves
296 			// so just ignore it
297 		}
298 	}
299 
300 	for (size_t i = 0; i < save->signs.size() && signs.size() < MAXSIGNS; i++)
301 	{
302 		if (save->signs[i].text[0])
303 		{
304 			sign tempSign = save->signs[i];
305 			tempSign.x += fullX;
306 			tempSign.y += fullY;
307 			signs.push_back(tempSign);
308 		}
309 	}
310 	for(int saveBlockX = 0; saveBlockX < save->blockWidth; saveBlockX++)
311 	{
312 		for(int saveBlockY = 0; saveBlockY < save->blockHeight; saveBlockY++)
313 		{
314 			if(save->blockMap[saveBlockY][saveBlockX])
315 			{
316 				bmap[saveBlockY+blockY][saveBlockX+blockX] = save->blockMap[saveBlockY][saveBlockX];
317 				fvx[saveBlockY+blockY][saveBlockX+blockX] = save->fanVelX[saveBlockY][saveBlockX];
318 				fvy[saveBlockY+blockY][saveBlockX+blockX] = save->fanVelY[saveBlockY][saveBlockX];
319 			}
320 			if (includePressure)
321 			{
322 				if (save->hasPressure)
323 				{
324 					pv[saveBlockY+blockY][saveBlockX+blockX] = save->pressure[saveBlockY][saveBlockX];
325 					vx[saveBlockY+blockY][saveBlockX+blockX] = save->velocityX[saveBlockY][saveBlockX];
326 					vy[saveBlockY+blockY][saveBlockX+blockX] = save->velocityY[saveBlockY][saveBlockX];
327 				}
328 				if (save->hasAmbientHeat)
329 					hv[saveBlockY+blockY][saveBlockX+blockX] = save->ambientHeat[saveBlockY][saveBlockX];
330 			}
331 		}
332 	}
333 
334 	gravWallChanged = true;
335 	air->RecalculateBlockAirMaps();
336 
337 	return 0;
338 }
339 
Save(bool includePressure)340 GameSave * Simulation::Save(bool includePressure)
341 {
342 	return Save(includePressure, 0, 0, XRES-1, YRES-1);
343 }
344 
Save(bool includePressure,int fullX,int fullY,int fullX2,int fullY2)345 GameSave * Simulation::Save(bool includePressure, int fullX, int fullY, int fullX2, int fullY2)
346 {
347 	int blockX, blockY, blockX2, blockY2, blockW, blockH;
348 	//Normalise incoming coords
349 	int swapTemp;
350 	if(fullY>fullY2)
351 	{
352 		swapTemp = fullY;
353 		fullY = fullY2;
354 		fullY2 = swapTemp;
355 	}
356 	if(fullX>fullX2)
357 	{
358 		swapTemp = fullX;
359 		fullX = fullX2;
360 		fullX2 = swapTemp;
361 	}
362 
363 	//Align coords to blockMap
364 	blockX = fullX/CELL;
365 	blockY = fullY/CELL;
366 
367 	blockX2 = (fullX2+CELL)/CELL;
368 	blockY2 = (fullY2+CELL)/CELL;
369 
370 	blockW = blockX2-blockX;
371 	blockH = blockY2-blockY;
372 
373 	GameSave * newSave = new GameSave(blockW, blockH);
374 
375 	int storedParts = 0;
376 	int elementCount[PT_NUM];
377 	std::fill(elementCount, elementCount+PT_NUM, 0);
378 	// Map of soap particles loaded into this save, old ID -> new ID
379 	// Now stores all particles, not just SOAP (but still only used for soap)
380 	std::map<unsigned int, unsigned int> particleMap;
381 	std::set<int> paletteSet;
382 	for (int i = 0; i < NPART; i++)
383 	{
384 		int x, y;
385 		x = int(parts[i].x + 0.5f);
386 		y = int(parts[i].y + 0.5f);
387 		if (parts[i].type && x >= fullX && y >= fullY && x <= fullX2 && y <= fullY2)
388 		{
389 			Particle tempPart = parts[i];
390 			tempPart.x -= blockX*CELL;
391 			tempPart.y -= blockY*CELL;
392 			if (elements[tempPart.type].Enabled)
393 			{
394 				particleMap.insert(std::pair<unsigned int, unsigned int>(i, storedParts));
395 				*newSave << tempPart;
396 				storedParts++;
397 				elementCount[tempPart.type]++;
398 
399 				paletteSet.insert(tempPart.type);
400 				if (GameSave::TypeInCtype(tempPart.type, tempPart.ctype))
401 					paletteSet.insert(tempPart.ctype);
402 				if (GameSave::TypeInTmp(tempPart.type))
403 					paletteSet.insert(TYP(tempPart.tmp));
404 				if (GameSave::TypeInTmp2(tempPart.type, tempPart.tmp2))
405 					paletteSet.insert(tempPart.tmp2);
406 			}
407 		}
408 	}
409 
410 	for (int ID : paletteSet)
411 		newSave->palette.push_back(GameSave::PaletteItem(elements[ID].Identifier, ID));
412 
413 	if (storedParts && elementCount[PT_SOAP])
414 	{
415 		// fix SOAP links using particleMap, a map of old particle ID -> new particle ID
416 		// loop through every new particle (saved into the save), and convert .tmp / .tmp2
417 		for (std::map<unsigned int, unsigned int>::iterator iter = particleMap.begin(), end = particleMap.end(); iter != end; ++iter)
418 		{
419 			int i = (*iter).second;
420 			if (newSave->particles[i].type != PT_SOAP)
421 				continue;
422 			if ((newSave->particles[i].ctype & 0x2) == 2)
423 			{
424 				std::map<unsigned int, unsigned int>::iterator n = particleMap.find(newSave->particles[i].tmp);
425 				if (n != end)
426 					newSave->particles[i].tmp = n->second;
427 				else
428 				{
429 					newSave->particles[i].tmp = 0;
430 					newSave->particles[i].ctype ^= 2;
431 				}
432 			}
433 			if ((newSave->particles[i].ctype & 0x4) == 4)
434 			{
435 				std::map<unsigned int, unsigned int>::iterator n = particleMap.find(newSave->particles[i].tmp2);
436 				if (n != end)
437 					newSave->particles[i].tmp2 = n->second;
438 				else
439 				{
440 					newSave->particles[i].tmp2 = 0;
441 					newSave->particles[i].ctype ^= 4;
442 				}
443 			}
444 		}
445 	}
446 
447 	for (size_t i = 0; i < MAXSIGNS && i < signs.size(); i++)
448 	{
449 		if(signs[i].text.length() && signs[i].x >= fullX && signs[i].y >= fullY && signs[i].x <= fullX2 && signs[i].y <= fullY2)
450 		{
451 			sign tempSign = signs[i];
452 			tempSign.x -= blockX*CELL;
453 			tempSign.y -= blockY*CELL;
454 			*newSave << tempSign;
455 		}
456 	}
457 
458 	for(int saveBlockX = 0; saveBlockX < newSave->blockWidth; saveBlockX++)
459 	{
460 		for(int saveBlockY = 0; saveBlockY < newSave->blockHeight; saveBlockY++)
461 		{
462 			if(bmap[saveBlockY+blockY][saveBlockX+blockX])
463 			{
464 				newSave->blockMap[saveBlockY][saveBlockX] = bmap[saveBlockY+blockY][saveBlockX+blockX];
465 				newSave->fanVelX[saveBlockY][saveBlockX] = fvx[saveBlockY+blockY][saveBlockX+blockX];
466 				newSave->fanVelY[saveBlockY][saveBlockX] = fvy[saveBlockY+blockY][saveBlockX+blockX];
467 			}
468 			if (includePressure)
469 			{
470 				newSave->pressure[saveBlockY][saveBlockX] = pv[saveBlockY+blockY][saveBlockX+blockX];
471 				newSave->velocityX[saveBlockY][saveBlockX] = vx[saveBlockY+blockY][saveBlockX+blockX];
472 				newSave->velocityY[saveBlockY][saveBlockX] = vy[saveBlockY+blockY][saveBlockX+blockX];
473 				newSave->ambientHeat[saveBlockY][saveBlockX] = hv[saveBlockY+blockY][saveBlockX+blockX];
474 			}
475 		}
476 	}
477 	if (includePressure)
478 	{
479 		newSave->hasPressure = true;
480 		newSave->hasAmbientHeat = true;
481 	}
482 
483 	newSave->stkm.rocketBoots1 = player.rocketBoots;
484 	newSave->stkm.rocketBoots2 = player2.rocketBoots;
485 	newSave->stkm.fan1 = player.fan;
486 	newSave->stkm.fan2 = player2.fan;
487 	for (unsigned char i = 0; i < MAX_FIGHTERS; i++)
488 	{
489 		if (fighters[i].rocketBoots)
490 			newSave->stkm.rocketBootsFigh.push_back(i);
491 		if (fighters[i].fan)
492 			newSave->stkm.fanFigh.push_back(i);
493 	}
494 
495 	SaveSimOptions(newSave);
496 	newSave->pmapbits = PMAPBITS;
497 	return newSave;
498 }
499 
SaveSimOptions(GameSave * gameSave)500 void Simulation::SaveSimOptions(GameSave * gameSave)
501 {
502 	if (!gameSave)
503 		return;
504 	gameSave->gravityMode = gravityMode;
505 	gameSave->airMode = air->airMode;
506 	gameSave->edgeMode = edgeMode;
507 	gameSave->legacyEnable = legacy_enable;
508 	gameSave->waterEEnabled = water_equal_test;
509 	gameSave->gravityEnable = grav->IsEnabled();
510 	gameSave->aheatEnable = aheat_enable;
511 }
512 
CreateSnapshot()513 Snapshot * Simulation::CreateSnapshot()
514 {
515 	Snapshot * snap = new Snapshot();
516 	snap->AirPressure.insert(snap->AirPressure.begin(), &pv[0][0], &pv[0][0]+((XRES/CELL)*(YRES/CELL)));
517 	snap->AirVelocityX.insert(snap->AirVelocityX.begin(), &vx[0][0], &vx[0][0]+((XRES/CELL)*(YRES/CELL)));
518 	snap->AirVelocityY.insert(snap->AirVelocityY.begin(), &vy[0][0], &vy[0][0]+((XRES/CELL)*(YRES/CELL)));
519 	snap->AmbientHeat.insert(snap->AmbientHeat.begin(), &hv[0][0], &hv[0][0]+((XRES/CELL)*(YRES/CELL)));
520 	snap->Particles.insert(snap->Particles.begin(), parts, parts+parts_lastActiveIndex+1);
521 	snap->PortalParticles.insert(snap->PortalParticles.begin(), &portalp[0][0][0], &portalp[CHANNELS-1][8-1][80-1]);
522 	snap->WirelessData.insert(snap->WirelessData.begin(), &wireless[0][0], &wireless[CHANNELS-1][2-1]);
523 	snap->GravVelocityX.insert(snap->GravVelocityX.begin(), gravx, gravx+((XRES/CELL)*(YRES/CELL)));
524 	snap->GravVelocityY.insert(snap->GravVelocityY.begin(), gravy, gravy+((XRES/CELL)*(YRES/CELL)));
525 	snap->GravValue.insert(snap->GravValue.begin(), gravp, gravp+((XRES/CELL)*(YRES/CELL)));
526 	snap->GravMap.insert(snap->GravMap.begin(), gravmap, gravmap+((XRES/CELL)*(YRES/CELL)));
527 	snap->BlockMap.insert(snap->BlockMap.begin(), &bmap[0][0], &bmap[0][0]+((XRES/CELL)*(YRES/CELL)));
528 	snap->ElecMap.insert(snap->ElecMap.begin(), &emap[0][0], &emap[0][0]+((XRES/CELL)*(YRES/CELL)));
529 	snap->FanVelocityX.insert(snap->FanVelocityX.begin(), &fvx[0][0], &fvx[0][0]+((XRES/CELL)*(YRES/CELL)));
530 	snap->FanVelocityY.insert(snap->FanVelocityY.begin(), &fvy[0][0], &fvy[0][0]+((XRES/CELL)*(YRES/CELL)));
531 	snap->stickmen.push_back(player2);
532 	snap->stickmen.push_back(player);
533 	snap->stickmen.insert(snap->stickmen.begin(), &fighters[0], &fighters[MAX_FIGHTERS]);
534 	snap->signs = signs;
535 	return snap;
536 }
537 
Restore(const Snapshot & snap)538 void Simulation::Restore(const Snapshot & snap)
539 {
540 	parts_lastActiveIndex = NPART-1;
541 	elementRecount = true;
542 	force_stacking_check = true;
543 
544 	std::copy(snap.AirPressure.begin(), snap.AirPressure.end(), &pv[0][0]);
545 	std::copy(snap.AirVelocityX.begin(), snap.AirVelocityX.end(), &vx[0][0]);
546 	std::copy(snap.AirVelocityY.begin(), snap.AirVelocityY.end(), &vy[0][0]);
547 	std::copy(snap.AmbientHeat.begin(), snap.AmbientHeat.end(), &hv[0][0]);
548 	for (int i = 0; i < NPART; i++)
549 		parts[i].type = 0;
550 	std::copy(snap.Particles.begin(), snap.Particles.end(), parts);
551 	parts_lastActiveIndex = NPART-1;
552 	RecalcFreeParticles(false);
553 	std::copy(snap.PortalParticles.begin(), snap.PortalParticles.end(), &portalp[0][0][0]);
554 	std::copy(snap.WirelessData.begin(), snap.WirelessData.end(), &wireless[0][0]);
555 	if (grav->IsEnabled())
556 	{
557 		grav->Clear();
558 		std::copy(snap.GravVelocityX.begin(), snap.GravVelocityX.end(), gravx);
559 		std::copy(snap.GravVelocityY.begin(), snap.GravVelocityY.end(), gravy);
560 		std::copy(snap.GravValue.begin(), snap.GravValue.end(), gravp);
561 		std::copy(snap.GravMap.begin(), snap.GravMap.end(), gravmap);
562 	}
563 	gravWallChanged = true;
564 	std::copy(snap.BlockMap.begin(), snap.BlockMap.end(), &bmap[0][0]);
565 	std::copy(snap.ElecMap.begin(), snap.ElecMap.end(), &emap[0][0]);
566 	std::copy(snap.FanVelocityX.begin(), snap.FanVelocityX.end(), &fvx[0][0]);
567 	std::copy(snap.FanVelocityY.begin(), snap.FanVelocityY.end(), &fvy[0][0]);
568 	std::copy(snap.stickmen.begin(), snap.stickmen.end()-2, &fighters[0]);
569 	player = snap.stickmen[snap.stickmen.size()-1];
570 	player2 = snap.stickmen[snap.stickmen.size()-2];
571 	signs = snap.signs;
572 }
573 
clear_area(int area_x,int area_y,int area_w,int area_h)574 void Simulation::clear_area(int area_x, int area_y, int area_w, int area_h)
575 {
576 	float fx = area_x-.5f, fy = area_y-.5f;
577 	for (int i = 0; i <= parts_lastActiveIndex; i++)
578 	{
579 		if (parts[i].type)
580 			if (parts[i].x >= fx && parts[i].x <= fx+area_w+1 && parts[i].y >= fy && parts[i].y <= fy+area_h+1)
581 				kill_part(i);
582 	}
583 	int cx1 = area_x/CELL, cy1 = area_y/CELL, cx2 = (area_x+area_w)/CELL, cy2 = (area_y+area_h)/CELL;
584 	for (int y = cy1; y <= cy2; y++)
585 	{
586 		for (int x = cx1; x <= cx2; x++)
587 		{
588 			if (bmap[y][x] == WL_GRAV)
589 				gravWallChanged = true;
590 			bmap[y][x] = 0;
591 			emap[y][x] = 0;
592 		}
593 	}
594 	for( int i = signs.size()-1; i >= 0; i--)
595 	{
596 		if (signs[i].text.length() && signs[i].x >= area_x && signs[i].y >= area_y && signs[i].x <= area_x+area_w && signs[i].y <= area_y+area_h)
597 		{
598 			signs.erase(signs.begin()+i);
599 		}
600 	}
601 }
602 
FloodFillPmapCheck(int x,int y,int type)603 bool Simulation::FloodFillPmapCheck(int x, int y, int type)
604 {
605 	if (type == 0)
606 		return !pmap[y][x] && !photons[y][x];
607 	if (elements[type].Properties&TYPE_ENERGY)
608 		return TYP(photons[y][x]) == type;
609 	else
610 		return TYP(pmap[y][x]) == type;
611 }
612 
getCoordStackSingleton()613 CoordStack& Simulation::getCoordStackSingleton()
614 {
615 	// Future-proofing in case Simulation is later multithreaded
616 	thread_local CoordStack cs;
617 	return cs;
618 }
619 
flood_prop(int x,int y,size_t propoffset,PropertyValue propvalue,StructProperty::PropertyType proptype)620 int Simulation::flood_prop(int x, int y, size_t propoffset, PropertyValue propvalue, StructProperty::PropertyType proptype)
621 {
622 	int i, x1, x2, dy = 1;
623 	int did_something = 0;
624 	int r = pmap[y][x];
625 	if (!r)
626 		r = photons[y][x];
627 	if (!r)
628 		return 0;
629 	int parttype = TYP(r);
630 	char * bitmap = (char*)malloc(XRES*YRES); //Bitmap for checking
631 	if (!bitmap) return -1;
632 	memset(bitmap, 0, XRES*YRES);
633 	try
634 	{
635 		CoordStack& cs = getCoordStackSingleton();
636 		cs.clear();
637 
638 		cs.push(x, y);
639 		do
640 		{
641 			cs.pop(x, y);
642 			x1 = x2 = x;
643 			while (x1>=CELL)
644 			{
645 				if (!FloodFillPmapCheck(x1-1, y, parttype) || bitmap[(y*XRES)+x1-1])
646 					break;
647 				x1--;
648 			}
649 			while (x2<XRES-CELL)
650 			{
651 				if (!FloodFillPmapCheck(x2+1, y, parttype) || bitmap[(y*XRES)+x2+1])
652 					break;
653 				x2++;
654 			}
655 			for (x=x1; x<=x2; x++)
656 			{
657 				i = pmap[y][x];
658 				if (!i)
659 					i = photons[y][x];
660 				if (!i)
661 					continue;
662 				switch (proptype) {
663 					case StructProperty::Float:
664 						*((float*)(((char*)&parts[ID(i)])+propoffset)) = propvalue.Float;
665 						break;
666 
667 					case StructProperty::ParticleType:
668 					case StructProperty::Integer:
669 						*((int*)(((char*)&parts[ID(i)])+propoffset)) = propvalue.Integer;
670 						break;
671 
672 					case StructProperty::UInteger:
673 						*((unsigned int*)(((char*)&parts[ID(i)])+propoffset)) = propvalue.UInteger;
674 						break;
675 
676 					default:
677 						break;
678 				}
679 				bitmap[(y*XRES)+x] = 1;
680 				did_something = 1;
681 			}
682 			if (y>=CELL+dy)
683 				for (x=x1; x<=x2; x++)
684 					if (FloodFillPmapCheck(x, y-dy, parttype) && !bitmap[((y-dy)*XRES)+x])
685 						cs.push(x, y-dy);
686 			if (y<YRES-CELL-dy)
687 				for (x=x1; x<=x2; x++)
688 					if (FloodFillPmapCheck(x, y+dy, parttype) && !bitmap[((y+dy)*XRES)+x])
689 						cs.push(x, y+dy);
690 		} while (cs.getSize()>0);
691 	}
692 	catch (std::exception& e)
693 	{
694 		std::cerr << e.what() << std::endl;
695 		free(bitmap);
696 		return -1;
697 	}
698 	free(bitmap);
699 	return did_something;
700 }
701 
GetSample(int x,int y)702 SimulationSample Simulation::GetSample(int x, int y)
703 {
704 	SimulationSample sample;
705 	sample.PositionX = x;
706 	sample.PositionY = y;
707 	if (x >= 0 && x < XRES && y >= 0 && y < YRES)
708 	{
709 		if (photons[y][x])
710 		{
711 			sample.particle = parts[ID(photons[y][x])];
712 			sample.ParticleID = ID(photons[y][x]);
713 		}
714 		else if (pmap[y][x])
715 		{
716 			sample.particle = parts[ID(pmap[y][x])];
717 			sample.ParticleID = ID(pmap[y][x]);
718 		}
719 		if (bmap[y/CELL][x/CELL])
720 		{
721 			sample.WallType = bmap[y/CELL][x/CELL];
722 		}
723 		sample.AirPressure = pv[y/CELL][x/CELL];
724 		sample.AirTemperature = hv[y/CELL][x/CELL];
725 		sample.AirVelocityX = vx[y/CELL][x/CELL];
726 		sample.AirVelocityY = vy[y/CELL][x/CELL];
727 
728 		if(grav->IsEnabled())
729 		{
730 			sample.Gravity = gravp[(y/CELL)*(XRES/CELL)+(x/CELL)];
731 			sample.GravityVelocityX = gravx[(y/CELL)*(XRES/CELL)+(x/CELL)];
732 			sample.GravityVelocityY = gravy[(y/CELL)*(XRES/CELL)+(x/CELL)];
733 		}
734 	}
735 	else
736 		sample.isMouseInSim = false;
737 
738 	sample.NumParts = NUM_PARTS;
739 	return sample;
740 }
741 
742 #define PMAP_CMP_CONDUCTIVE(pmap, t) (TYP(pmap)==(t) || (TYP(pmap)==PT_SPRK && parts[ID(pmap)].ctype==(t)))
743 
FloodINST(int x,int y)744 int Simulation::FloodINST(int x, int y)
745 {
746 	const int cm = PT_INST;
747 	int x1, x2;
748 	int created_something = 0;
749 
750 	if (TYP(pmap[y][x]) != cm || parts[ID(pmap[y][x])].life != 0)
751 		return 1;
752 
753 	CoordStack& cs = getCoordStackSingleton();
754 	cs.clear();
755 
756 	cs.push(x, y);
757 
758 	try
759 	{
760 		do
761 		{
762 			cs.pop(x, y);
763 			x1 = x2 = x;
764 			// go left as far as possible
765 			while (x1>=CELL)
766 			{
767 				if (TYP(pmap[y][x1-1])!=cm || parts[ID(pmap[y][x1-1])].life!=0)
768 				{
769 					break;
770 				}
771 				x1--;
772 			}
773 			// go right as far as possible
774 			while (x2<XRES-CELL)
775 			{
776 				if (TYP(pmap[y][x2+1])!=cm || parts[ID(pmap[y][x2+1])].life!=0)
777 				{
778 					break;
779 				}
780 				x2++;
781 			}
782 			// fill span
783 			for (x=x1; x<=x2; x++)
784 			{
785 				if (create_part(-1, x, y, PT_SPRK)>=0)
786 					created_something = 1;
787 			}
788 
789 			// add vertically adjacent pixels to stack
790 			// (wire crossing for INST)
791 			if (y>=CELL+1 && x1==x2 &&
792 					PMAP_CMP_CONDUCTIVE(pmap[y-1][x1-1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y-1][x1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y-1][x1+1], cm) &&
793 					!PMAP_CMP_CONDUCTIVE(pmap[y-2][x1-1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y-2][x1], cm) && !PMAP_CMP_CONDUCTIVE(pmap[y-2][x1+1], cm))
794 			{
795 				// travelling vertically up, skipping a horizontal line
796 				if (TYP(pmap[y-2][x1])==cm && !parts[ID(pmap[y-2][x1])].life)
797 				{
798 						cs.push(x1, y-2);
799 				}
800 			}
801 			else if (y>=CELL+1)
802 			{
803 				for (x=x1; x<=x2; x++)
804 				{
805 					if (TYP(pmap[y-1][x])==cm && !parts[ID(pmap[y-1][x])].life)
806 					{
807 						if (x==x1 || x==x2 || y>=YRES-CELL-1 || !PMAP_CMP_CONDUCTIVE(pmap[y+1][x], cm) || PMAP_CMP_CONDUCTIVE(pmap[y+1][x+1], cm) || PMAP_CMP_CONDUCTIVE(pmap[y+1][x-1], cm))
808 						{
809 							// if at the end of a horizontal section, or if it's a T junction or not a 1px wire crossing
810 							cs.push(x, y-1);
811 						}
812 					}
813 				}
814 			}
815 
816 			if (y<YRES-CELL-1 && x1==x2 &&
817 					PMAP_CMP_CONDUCTIVE(pmap[y+1][x1-1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y+1][x1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y+1][x1+1], cm) &&
818 					!PMAP_CMP_CONDUCTIVE(pmap[y+2][x1-1], cm) && PMAP_CMP_CONDUCTIVE(pmap[y+2][x1], cm) && !PMAP_CMP_CONDUCTIVE(pmap[y+2][x1+1], cm))
819 			{
820 				// travelling vertically down, skipping a horizontal line
821 				if (TYP(pmap[y+2][x1])==cm && !parts[ID(pmap[y+2][x1])].life)
822 				{
823 					cs.push(x1, y+2);
824 				}
825 			}
826 			else if (y<YRES-CELL-1)
827 			{
828 				for (x=x1; x<=x2; x++)
829 				{
830 					if (TYP(pmap[y+1][x])==cm && !parts[ID(pmap[y+1][x])].life)
831 					{
832 						if (x==x1 || x==x2 || y<0 || !PMAP_CMP_CONDUCTIVE(pmap[y-1][x], cm) || PMAP_CMP_CONDUCTIVE(pmap[y-1][x+1], cm) || PMAP_CMP_CONDUCTIVE(pmap[y-1][x-1], cm))
833 						{
834 							// if at the end of a horizontal section, or if it's a T junction or not a 1px wire crossing
835 							cs.push(x, y+1);
836 						}
837 
838 					}
839 				}
840 			}
841 		} while (cs.getSize()>0);
842 	}
843 	catch (std::exception& e)
844 	{
845 		std::cerr << e.what() << std::endl;
846 		return -1;
847 	}
848 
849 	return created_something;
850 }
851 
flood_water(int x,int y,int i)852 bool Simulation::flood_water(int x, int y, int i)
853 {
854 	int x1, x2, originalY = y;
855 	int r = pmap[y][x];
856 	if (!r)
857 		return false;
858 
859 	// Bitmap for checking where we've already looked
860 	auto bitmapPtr = std::unique_ptr<char[]>(new char[XRES * YRES]);
861 	char *bitmap = bitmapPtr.get();
862 	std::fill(&bitmap[0], &bitmap[XRES * YRES], 0);
863 
864 	try
865 	{
866 		CoordStack& cs = getCoordStackSingleton();
867 		cs.clear();
868 
869 		cs.push(x, y);
870 		do
871 		{
872 			cs.pop(x, y);
873 			x1 = x2 = x;
874 			while (x1 >= CELL)
875 			{
876 				if (elements[TYP(pmap[y][x1 - 1])].Falldown != 2 || bitmap[(y * XRES) + x1 - 1])
877 					break;
878 				x1--;
879 			}
880 			while (x2 < XRES-CELL)
881 			{
882 				if (elements[TYP(pmap[y][x2 + 1])].Falldown != 2 || bitmap[(y * XRES) + x1 - 1])
883 					break;
884 				x2++;
885 			}
886 			for (int x = x1; x <= x2; x++)
887 			{
888 				if ((y - 1) > originalY && !pmap[y - 1][x])
889 				{
890 					// Try to move the water to a random position on this line, because there's probably a free location somewhere
891 					int randPos = RNG::Ref().between(x, x2);
892 					if (!pmap[y - 1][randPos] && eval_move(parts[i].type, randPos, y - 1, nullptr))
893 						x = randPos;
894 					// Couldn't move to random position, so try the original position on the left
895 					else if (!eval_move(parts[i].type, x, y - 1, nullptr))
896 						continue;
897 
898 					int oldx = (int)(parts[i].x + 0.5f);
899 					int oldy = (int)(parts[i].y + 0.5f);
900 					pmap[y - 1][x] = pmap[oldy][oldx];
901 					pmap[oldy][oldx] = 0;
902 					parts[i].x = x;
903 					parts[i].y = y - 1;
904 					return true;
905 				}
906 
907 				bitmap[(y * XRES) + x] = 1;
908 			}
909 			if (y >= CELL + 1)
910 				for (int x = x1; x <= x2; x++)
911 					if (elements[TYP(pmap[y - 1][x])].Falldown == 2 && !bitmap[((y - 1) * XRES) + x])
912 						cs.push(x, y - 1);
913 			if (y < YRES - CELL - 1)
914 				for (int x = x1; x <= x2; x++)
915 					if (elements[TYP(pmap[y + 1][x])].Falldown == 2 && !bitmap[((y + 1) * XRES) + x])
916 						cs.push(x, y + 1);
917 		} while (cs.getSize() > 0);
918 	}
919 	catch (std::exception &e)
920 	{
921 		std::cerr << e.what() << std::endl;
922 		return false;
923 	}
924 	return false;
925 }
926 
SetDecoSpace(int newDecoSpace)927 void Simulation::SetDecoSpace(int newDecoSpace)
928 {
929 	switch (newDecoSpace)
930 	{
931 	case 0: // sRGB
932 	default: // anything stupid
933 		deco_space = 0;
934 		break;
935 
936 	case 1: // linear
937 		deco_space = 1;
938 		break;
939 
940 	case 2: // Gamma = 2.2
941 		deco_space = 2;
942 		break;
943 
944 	case 3: // Gamma = 1.8
945 		deco_space = 3;
946 		break;
947 	}
948 }
949 
SetEdgeMode(int newEdgeMode)950 void Simulation::SetEdgeMode(int newEdgeMode)
951 {
952 	edgeMode = newEdgeMode;
953 	switch(edgeMode)
954 	{
955 	case 0:
956 	case 2:
957 		for(int i = 0; i<(XRES/CELL); i++)
958 		{
959 			bmap[0][i] = 0;
960 			bmap[YRES/CELL-1][i] = 0;
961 		}
962 		for(int i = 1; i<((YRES/CELL)-1); i++)
963 		{
964 			bmap[i][0] = 0;
965 			bmap[i][XRES/CELL-1] = 0;
966 		}
967 		break;
968 	case 1:
969 		int i;
970 		for(i=0; i<(XRES/CELL); i++)
971 		{
972 			bmap[0][i] = WL_WALL;
973 			bmap[YRES/CELL-1][i] = WL_WALL;
974 		}
975 		for(i=1; i<((YRES/CELL)-1); i++)
976 		{
977 			bmap[i][0] = WL_WALL;
978 			bmap[i][XRES/CELL-1] = WL_WALL;
979 		}
980 		break;
981 	default:
982 		SetEdgeMode(0);
983 	}
984 }
985 
ApplyDecoration(int x,int y,int colR_,int colG_,int colB_,int colA_,int mode)986 void Simulation::ApplyDecoration(int x, int y, int colR_, int colG_, int colB_, int colA_, int mode)
987 {
988 	int rp;
989 	float tr, tg, tb, ta, colR = colR_, colG = colG_, colB = colB_, colA = colA_;
990 	float strength = 0.01f;
991 	rp = pmap[y][x];
992 	if (!rp)
993 		rp = photons[y][x];
994 	if (!rp)
995 		return;
996 
997 	ta = (parts[ID(rp)].dcolour>>24)&0xFF;
998 	tr = (parts[ID(rp)].dcolour>>16)&0xFF;
999 	tg = (parts[ID(rp)].dcolour>>8)&0xFF;
1000 	tb = (parts[ID(rp)].dcolour)&0xFF;
1001 
1002 	ta /= 255.0f; tr /= 255.0f; tg /= 255.0f; tb /= 255.0f;
1003 	colR /= 255.0f; colG /= 255.0f; colB /= 255.0f; colA /= 255.0f;
1004 
1005 	if (mode == DECO_DRAW)
1006 	{
1007 		ta = colA;
1008 		tr = colR;
1009 		tg = colG;
1010 		tb = colB;
1011 	}
1012 	else if (mode == DECO_CLEAR)
1013 	{
1014 		ta = tr = tg = tb = 0.0f;
1015 	}
1016 	else if (mode == DECO_ADD)
1017 	{
1018 		//ta += (colA*strength)*colA;
1019 		tr += (colR*strength)*colA;
1020 		tg += (colG*strength)*colA;
1021 		tb += (colB*strength)*colA;
1022 	}
1023 	else if (mode == DECO_SUBTRACT)
1024 	{
1025 		//ta -= (colA*strength)*colA;
1026 		tr -= (colR*strength)*colA;
1027 		tg -= (colG*strength)*colA;
1028 		tb -= (colB*strength)*colA;
1029 	}
1030 	else if (mode == DECO_MULTIPLY)
1031 	{
1032 		tr *= 1.0f+(colR*strength)*colA;
1033 		tg *= 1.0f+(colG*strength)*colA;
1034 		tb *= 1.0f+(colB*strength)*colA;
1035 	}
1036 	else if (mode == DECO_DIVIDE)
1037 	{
1038 		tr /= 1.0f+(colR*strength)*colA;
1039 		tg /= 1.0f+(colG*strength)*colA;
1040 		tb /= 1.0f+(colB*strength)*colA;
1041 	}
1042 	else if (mode == DECO_SMUDGE)
1043 	{
1044 		if (x >= CELL && x < XRES-CELL && y >= CELL && y < YRES-CELL)
1045 		{
1046 			float tas = 0.0f, trs = 0.0f, tgs = 0.0f, tbs = 0.0f;
1047 
1048 			int rx, ry;
1049 			float num = 0;
1050 			for (rx=-2; rx<3; rx++)
1051 				for (ry=-2; ry<3; ry++)
1052 				{
1053 					if (abs(rx)+abs(ry) > 2 && TYP(pmap[y+ry][x+rx]) && parts[ID(pmap[y+ry][x+rx])].dcolour)
1054 					{
1055 						Particle part = parts[ID(pmap[y+ry][x+rx])];
1056 						num += 1.0f;
1057 						float pa = ((float)((part.dcolour>>24)&0xFF)) / 255.f;
1058 						float pr = ((float)((part.dcolour>>16)&0xFF)) / 255.f;
1059 						float pg = ((float)((part.dcolour>> 8)&0xFF)) / 255.f;
1060 						float pb = ((float)((part.dcolour    )&0xFF)) / 255.f;
1061 						switch (deco_space)
1062 						{
1063 						case 0: // sRGB
1064 							pa = (pa <= 0.04045f) ? (pa / 12.92f) : pow((pa + 0.055f) / 1.055f, 2.4f);
1065 							pr = (pr <= 0.04045f) ? (pr / 12.92f) : pow((pr + 0.055f) / 1.055f, 2.4f);
1066 							pg = (pg <= 0.04045f) ? (pg / 12.92f) : pow((pg + 0.055f) / 1.055f, 2.4f);
1067 							pb = (pb <= 0.04045f) ? (pb / 12.92f) : pow((pb + 0.055f) / 1.055f, 2.4f);
1068 							break;
1069 
1070 						case 1: // linear
1071 							break;
1072 
1073 						case 2: // Gamma = 2.2
1074 							pa = pow(pa, 2.2f);
1075 							pr = pow(pr, 2.2f);
1076 							pg = pow(pg, 2.2f);
1077 							pb = pow(pb, 2.2f);
1078 							break;
1079 
1080 						case 3: // Gamma = 1.8
1081 							pa = pow(pa, 1.8f);
1082 							pr = pow(pr, 1.8f);
1083 							pg = pow(pg, 1.8f);
1084 							pb = pow(pb, 1.8f);
1085 							break;
1086 						}
1087 						tas += pa;
1088 						trs += pr;
1089 						tgs += pg;
1090 						tbs += pb;
1091 					}
1092 				}
1093 			if (num == 0)
1094 				return;
1095 			ta = tas / num;
1096 			tr = trs / num;
1097 			tg = tgs / num;
1098 			tb = tbs / num;
1099 			switch (deco_space)
1100 			{
1101 			case 0: // sRGB
1102 				ta = (ta <= 0.0031308f) ? (ta * 12.92f) : (1.055f * pow(ta, 1.f / 2.4f) - 0.055f);
1103 				tr = (tr <= 0.0031308f) ? (tr * 12.92f) : (1.055f * pow(tr, 1.f / 2.4f) - 0.055f);
1104 				tg = (tg <= 0.0031308f) ? (tg * 12.92f) : (1.055f * pow(tg, 1.f / 2.4f) - 0.055f);
1105 				tb = (tb <= 0.0031308f) ? (tb * 12.92f) : (1.055f * pow(tb, 1.f / 2.4f) - 0.055f);
1106 				break;
1107 
1108 			case 1: // linear
1109 				break;
1110 
1111 			case 2: // Gamma = 2.2
1112 				ta = pow(ta, 1.f / 2.2f);
1113 				tr = pow(tr, 1.f / 2.2f);
1114 				tg = pow(tg, 1.f / 2.2f);
1115 				tb = pow(tb, 1.f / 2.2f);
1116 				break;
1117 
1118 			case 3: // Gamma = 1.8
1119 				ta = pow(ta, 1.f / 1.8f);
1120 				tr = pow(tr, 1.f / 1.8f);
1121 				tg = pow(tg, 1.f / 1.8f);
1122 				tb = pow(tb, 1.f / 1.8f);
1123 				break;
1124 			}
1125 			if (!parts[ID(rp)].dcolour)
1126 				ta -= 3/255.0f;
1127 		}
1128 	}
1129 
1130 	ta *= 255.0f; tr *= 255.0f; tg *= 255.0f; tb *= 255.0f;
1131 	ta += .5f; tr += .5f; tg += .5f; tb += .5f;
1132 
1133 	colA_ = ta;
1134 	colR_ = tr;
1135 	colG_ = tg;
1136 	colB_ = tb;
1137 
1138 	if(colA_ > 255)
1139 		colA_ = 255;
1140 	else if(colA_ < 0)
1141 		colA_ = 0;
1142 	if(colR_ > 255)
1143 		colR_ = 255;
1144 	else if(colR_ < 0)
1145 		colR_ = 0;
1146 	if(colG_ > 255)
1147 		colG_ = 255;
1148 	else if(colG_ < 0)
1149 		colG_ = 0;
1150 	if(colB_ > 255)
1151 		colB_ = 255;
1152 	else if(colB_ < 0)
1153 		colB_ = 0;
1154 	parts[ID(rp)].dcolour = ((colA_<<24)|(colR_<<16)|(colG_<<8)|colB_);
1155 }
1156 
ApplyDecorationPoint(int positionX,int positionY,int colR,int colG,int colB,int colA,int mode,Brush * cBrush)1157 void Simulation::ApplyDecorationPoint(int positionX, int positionY, int colR, int colG, int colB, int colA, int mode, Brush * cBrush)
1158 {
1159 	if(cBrush)
1160 	{
1161 		int radiusX = cBrush->GetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y;
1162 		unsigned char *bitmap = cBrush->GetBitmap();
1163 
1164 		for(int y = 0; y < sizeY; y++)
1165 		{
1166 			for(int x = 0; x < sizeX; x++)
1167 			{
1168 				if(bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES))
1169 				{
1170 					ApplyDecoration(positionX+(x-radiusX), positionY+(y-radiusY), colR, colG, colB, colA, mode);
1171 				}
1172 			}
1173 		}
1174 	}
1175 }
1176 
ApplyDecorationLine(int x1,int y1,int x2,int y2,int colR,int colG,int colB,int colA,int mode,Brush * cBrush)1177 void Simulation::ApplyDecorationLine(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode, Brush * cBrush)
1178 {
1179 	bool reverseXY = abs(y2-y1) > abs(x2-x1);
1180 	int x, y, dx, dy, sy, rx = 0, ry = 0;
1181 	float e = 0.0f, de;
1182 
1183 	if(cBrush)
1184 	{
1185 		rx = cBrush->GetRadius().X;
1186 		ry = cBrush->GetRadius().Y;
1187 	}
1188 
1189 	if (reverseXY)
1190 	{
1191 		y = x1;
1192 		x1 = y1;
1193 		y1 = y;
1194 		y = x2;
1195 		x2 = y2;
1196 		y2 = y;
1197 	}
1198 	if (x1 > x2)
1199 	{
1200 		y = x1;
1201 		x1 = x2;
1202 		x2 = y;
1203 		y = y1;
1204 		y1 = y2;
1205 		y2 = y;
1206 	}
1207 	dx = x2 - x1;
1208 	dy = abs(y2 - y1);
1209 	if (dx)
1210 		de = dy/(float)dx;
1211 	else
1212 		de = 0.0f;
1213 	y = y1;
1214 	sy = (y1<y2) ? 1 : -1;
1215 	for (x=x1; x<=x2; x++)
1216 	{
1217 		if (reverseXY)
1218 			ApplyDecorationPoint(y, x, colR, colG, colB, colA, mode, cBrush);
1219 		else
1220 			ApplyDecorationPoint(x, y, colR, colG, colB, colA, mode, cBrush);
1221 		e += de;
1222 		if (e >= 0.5f)
1223 		{
1224 			y += sy;
1225 			if (!(rx+ry))
1226 			{
1227 				if (reverseXY)
1228 					ApplyDecorationPoint(y, x, colR, colG, colB, colA, mode, cBrush);
1229 				else
1230 					ApplyDecorationPoint(x, y, colR, colG, colB, colA, mode, cBrush);
1231 			}
1232 			e -= 1.0f;
1233 		}
1234 	}
1235 }
1236 
ApplyDecorationBox(int x1,int y1,int x2,int y2,int colR,int colG,int colB,int colA,int mode)1237 void Simulation::ApplyDecorationBox(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode)
1238 {
1239 	int i, j;
1240 
1241 	if (x1>x2)
1242 	{
1243 		i = x2;
1244 		x2 = x1;
1245 		x1 = i;
1246 	}
1247 	if (y1>y2)
1248 	{
1249 		j = y2;
1250 		y2 = y1;
1251 		y1 = j;
1252 	}
1253 	for (j=y1; j<=y2; j++)
1254 		for (i=x1; i<=x2; i++)
1255 			ApplyDecoration(i, j, colR, colG, colB, colA, mode);
1256 }
1257 
ColorCompare(Renderer * ren,int x,int y,int replaceR,int replaceG,int replaceB)1258 bool Simulation::ColorCompare(Renderer *ren, int x, int y, int replaceR, int replaceG, int replaceB)
1259 {
1260 	pixel pix = ren->vid[x+y*WINDOWW];
1261 	int r = PIXR(pix);
1262 	int g = PIXG(pix);
1263 	int b = PIXB(pix);
1264 	int diff = std::abs(replaceR-r) + std::abs(replaceG-g) + std::abs(replaceB-b);
1265 	return diff < 15;
1266 }
1267 
ApplyDecorationFill(Renderer * ren,int x,int y,int colR,int colG,int colB,int colA,int replaceR,int replaceG,int replaceB)1268 void Simulation::ApplyDecorationFill(Renderer *ren, int x, int y, int colR, int colG, int colB, int colA, int replaceR, int replaceG, int replaceB)
1269 {
1270 	int x1, x2;
1271 	char *bitmap = (char*)malloc(XRES*YRES); //Bitmap for checking
1272 	if (!bitmap)
1273 		return;
1274 	memset(bitmap, 0, XRES*YRES);
1275 
1276 	if (!ColorCompare(ren, x, y, replaceR, replaceG, replaceB)) {
1277 		free(bitmap);
1278 		return;
1279 	}
1280 
1281 	try
1282 	{
1283 		CoordStack& cs = getCoordStackSingleton();
1284 		cs.clear();
1285 
1286 		cs.push(x, y);
1287 		do
1288 		{
1289 			cs.pop(x, y);
1290 			x1 = x2 = x;
1291 			// go left as far as possible
1292 			while (x1>0)
1293 			{
1294 				if (bitmap[(x1-1)+y*XRES] || !ColorCompare(ren, x1-1, y, replaceR, replaceG, replaceB))
1295 				{
1296 					break;
1297 				}
1298 				x1--;
1299 			}
1300 			// go right as far as possible
1301 			while (x2<XRES-1)
1302 			{
1303 				if (bitmap[(x1+1)+y*XRES] || !ColorCompare(ren, x2+1, y, replaceR, replaceG, replaceB))
1304 				{
1305 					break;
1306 				}
1307 				x2++;
1308 			}
1309 			// fill span
1310 			for (x=x1; x<=x2; x++)
1311 			{
1312 				ApplyDecoration(x, y, colR, colG, colB, colA, DECO_DRAW);
1313 				bitmap[x+y*XRES] = 1;
1314 			}
1315 
1316 			if (y >= 1)
1317 				for (x=x1; x<=x2; x++)
1318 					if (!bitmap[x+(y-1)*XRES] && ColorCompare(ren, x, y-1, replaceR, replaceG, replaceB))
1319 						cs.push(x, y-1);
1320 
1321 			if (y < YRES-1)
1322 				for (x=x1; x<=x2; x++)
1323 					if (!bitmap[x+(y+1)*XRES] && ColorCompare(ren, x, y+1, replaceR, replaceG, replaceB))
1324 						cs.push(x, y+1);
1325 		} while (cs.getSize() > 0);
1326 	}
1327 	catch (std::exception& e)
1328 	{
1329 		std::cerr << e.what() << std::endl;
1330 		free(bitmap);
1331 		return;
1332 	}
1333 	free(bitmap);
1334 }
1335 
Tool(int x,int y,int tool,int brushX,int brushY,float strength)1336 int Simulation::Tool(int x, int y, int tool, int brushX, int brushY, float strength)
1337 {
1338 	Particle * cpart = NULL;
1339 	int r;
1340 	if ((r = pmap[y][x]))
1341 		cpart = &(parts[ID(r)]);
1342 	else if ((r = photons[y][x]))
1343 		cpart = &(parts[ID(r)]);
1344 	return tools[tool].Perform(this, cpart, x, y, brushX, brushY, strength);
1345 }
1346 
ToolBrush(int positionX,int positionY,int tool,Brush * cBrush,float strength)1347 int Simulation::ToolBrush(int positionX, int positionY, int tool, Brush * cBrush, float strength)
1348 {
1349 	if(cBrush)
1350 	{
1351 		int radiusX = cBrush->GetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y;
1352 		unsigned char *bitmap = cBrush->GetBitmap();
1353 		for(int y = 0; y < sizeY; y++)
1354 			for(int x = 0; x < sizeX; x++)
1355 				if(bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES))
1356 					Tool(positionX + (x - radiusX), positionY + (y - radiusY), tool, positionX, positionY, strength);
1357 	}
1358 	return 0;
1359 }
1360 
ToolLine(int x1,int y1,int x2,int y2,int tool,Brush * cBrush,float strength)1361 void Simulation::ToolLine(int x1, int y1, int x2, int y2, int tool, Brush * cBrush, float strength)
1362 {
1363 	bool reverseXY = abs(y2-y1) > abs(x2-x1);
1364 	int x, y, dx, dy, sy, rx = cBrush->GetRadius().X, ry = cBrush->GetRadius().Y;
1365 	float e = 0.0f, de;
1366 	if (reverseXY)
1367 	{
1368 		y = x1;
1369 		x1 = y1;
1370 		y1 = y;
1371 		y = x2;
1372 		x2 = y2;
1373 		y2 = y;
1374 	}
1375 	if (x1 > x2)
1376 	{
1377 		y = x1;
1378 		x1 = x2;
1379 		x2 = y;
1380 		y = y1;
1381 		y1 = y2;
1382 		y2 = y;
1383 	}
1384 	dx = x2 - x1;
1385 	dy = abs(y2 - y1);
1386 	if (dx)
1387 		de = dy/(float)dx;
1388 	else
1389 		de = 0.0f;
1390 	y = y1;
1391 	sy = (y1<y2) ? 1 : -1;
1392 	for (x=x1; x<=x2; x++)
1393 	{
1394 		if (reverseXY)
1395 			ToolBrush(y, x, tool, cBrush, strength);
1396 		else
1397 			ToolBrush(x, y, tool, cBrush, strength);
1398 		e += de;
1399 		if (e >= 0.5f)
1400 		{
1401 			y += sy;
1402 			if (!(rx+ry) && ((y1<y2) ? (y<=y2) : (y>=y2)))
1403 			{
1404 				if (reverseXY)
1405 					ToolBrush(y, x, tool, cBrush, strength);
1406 				else
1407 					ToolBrush(x, y, tool, cBrush, strength);
1408 			}
1409 			e -= 1.0f;
1410 		}
1411 	}
1412 }
ToolBox(int x1,int y1,int x2,int y2,int tool,float strength)1413 void Simulation::ToolBox(int x1, int y1, int x2, int y2, int tool, float strength)
1414 {
1415 	int brushX, brushY;
1416 	brushX = ((x1 + x2) / 2);
1417 	brushY = ((y1 + y2) / 2);
1418 	int i, j;
1419 	if (x1>x2)
1420 	{
1421 		i = x2;
1422 		x2 = x1;
1423 		x1 = i;
1424 	}
1425 	if (y1>y2)
1426 	{
1427 		j = y2;
1428 		y2 = y1;
1429 		y1 = j;
1430 	}
1431 	for (j=y1; j<=y2; j++)
1432 		for (i=x1; i<=x2; i++)
1433 			Tool(i, j, tool, brushX, brushY, strength);
1434 }
1435 
CreateWalls(int x,int y,int rx,int ry,int wall,Brush * cBrush)1436 int Simulation::CreateWalls(int x, int y, int rx, int ry, int wall, Brush * cBrush)
1437 {
1438 	if(cBrush)
1439 	{
1440 		rx = cBrush->GetRadius().X;
1441 		ry = cBrush->GetRadius().Y;
1442 	}
1443 
1444 	ry = ry/CELL;
1445 	rx = rx/CELL;
1446 	x = x/CELL;
1447 	y = y/CELL;
1448 	x -= rx;
1449 	y -= ry;
1450 	for (int wallX = x; wallX <= x+rx+rx; wallX++)
1451 	{
1452 		for (int wallY = y; wallY <= y+ry+ry; wallY++)
1453 		{
1454 			if (wallX >= 0 && wallX < XRES/CELL && wallY >= 0 && wallY < YRES/CELL)
1455 			{
1456 				if (wall == WL_FAN)
1457 				{
1458 					fvx[wallY][wallX] = 0.0f;
1459 					fvy[wallY][wallX] = 0.0f;
1460 				}
1461 				else if (wall == WL_STREAM)
1462 				{
1463 					wallX = x + rx;
1464 					wallY = y + ry;
1465 					//streamlines can't be drawn next to each other
1466 					for (int tempY = wallY-1; tempY < wallY+2; tempY++)
1467 						for (int tempX = wallX-1; tempX < wallX+2; tempX++)
1468 						{
1469 							if (tempX >= 0 && tempX < XRES/CELL && tempY >= 0 && tempY < YRES/CELL && bmap[tempY][tempX] == WL_STREAM)
1470 								return 1;
1471 						}
1472 				}
1473 				if (wall == WL_GRAV || bmap[wallY][wallX] == WL_GRAV)
1474 					gravWallChanged = true;
1475 
1476 				if (wall == WL_ERASEALL)
1477 				{
1478 					for (int i = 0; i < CELL; i++)
1479 						for (int j = 0; j < CELL; j++)
1480 						{
1481 							delete_part(wallX*CELL+i, wallY*CELL+j);
1482 						}
1483 					for (int i = signs.size()-1; i >= 0; i--)
1484 						if (signs[i].x >= wallX*CELL && signs[i].y >= wallY*CELL && signs[i].x <= (wallX+1)*CELL && signs[i].y <= (wallY+1)*CELL)
1485 							signs.erase(signs.begin()+i);
1486 					bmap[wallY][wallX] = 0;
1487 				}
1488 				else
1489 					bmap[wallY][wallX] = wall;
1490 			}
1491 		}
1492 	}
1493 	return 1;
1494 }
1495 
CreateWallLine(int x1,int y1,int x2,int y2,int rx,int ry,int wall,Brush * cBrush)1496 void Simulation::CreateWallLine(int x1, int y1, int x2, int y2, int rx, int ry, int wall, Brush * cBrush)
1497 {
1498 	int x, y, dx, dy, sy;
1499 	bool reverseXY = abs(y2-y1) > abs(x2-x1);
1500 	float e = 0.0f, de;
1501 	if (reverseXY)
1502 	{
1503 		y = x1;
1504 		x1 = y1;
1505 		y1 = y;
1506 		y = x2;
1507 		x2 = y2;
1508 		y2 = y;
1509 	}
1510 	if (x1 > x2)
1511 	{
1512 		y = x1;
1513 		x1 = x2;
1514 		x2 = y;
1515 		y = y1;
1516 		y1 = y2;
1517 		y2 = y;
1518 	}
1519 	dx = x2 - x1;
1520 	dy = abs(y2 - y1);
1521 	if (dx)
1522 		de = dy/(float)dx;
1523 	else
1524 		de = 0.0f;
1525 	y = y1;
1526 	sy = (y1<y2) ? 1 : -1;
1527 	for (x=x1; x<=x2; x++)
1528 	{
1529 		if (reverseXY)
1530 			CreateWalls(y, x, rx, ry, wall, cBrush);
1531 		else
1532 			CreateWalls(x, y, rx, ry, wall, cBrush);
1533 		e += de;
1534 		if (e >= 0.5f)
1535 		{
1536 			y += sy;
1537 			if ((y1<y2) ? (y<=y2) : (y>=y2))
1538 			{
1539 				if (reverseXY)
1540 					CreateWalls(y, x, rx, ry, wall, cBrush);
1541 				else
1542 					CreateWalls(x, y, rx, ry, wall, cBrush);
1543 			}
1544 			e -= 1.0f;
1545 		}
1546 	}
1547 }
1548 
CreateWallBox(int x1,int y1,int x2,int y2,int wall)1549 void Simulation::CreateWallBox(int x1, int y1, int x2, int y2, int wall)
1550 {
1551 	int i, j;
1552 	if (x1>x2)
1553 	{
1554 		i = x2;
1555 		x2 = x1;
1556 		x1 = i;
1557 	}
1558 	if (y1>y2)
1559 	{
1560 		j = y2;
1561 		y2 = y1;
1562 		y1 = j;
1563 	}
1564 	for (j=y1; j<=y2; j++)
1565 		for (i=x1; i<=x2; i++)
1566 			CreateWalls(i, j, 0, 0, wall, NULL);
1567 }
1568 
FloodWalls(int x,int y,int wall,int bm)1569 int Simulation::FloodWalls(int x, int y, int wall, int bm)
1570 {
1571 	int x1, x2, dy = CELL;
1572 	if (bm==-1)
1573 	{
1574 		if (wall==WL_ERASE || wall==WL_ERASEALL)
1575 		{
1576 			bm = bmap[y/CELL][x/CELL];
1577 			if (!bm)
1578 				return 0;
1579 		}
1580 		else
1581 			bm = 0;
1582 	}
1583 
1584 	if (bmap[y/CELL][x/CELL]!=bm)
1585 		return 1;
1586 
1587 	// go left as far as possible
1588 	x1 = x2 = x;
1589 	while (x1>=CELL)
1590 	{
1591 		if (bmap[y/CELL][(x1-1)/CELL]!=bm)
1592 		{
1593 			break;
1594 		}
1595 		x1--;
1596 	}
1597 	while (x2<XRES-CELL)
1598 	{
1599 		if (bmap[y/CELL][(x2+1)/CELL]!=bm)
1600 		{
1601 			break;
1602 		}
1603 		x2++;
1604 	}
1605 
1606 	// fill span
1607 	for (x=x1; x<=x2; x++)
1608 	{
1609 		if (!CreateWalls(x, y, 0, 0, wall, NULL))
1610 			return 0;
1611 	}
1612 	// fill children
1613 	if (y>=CELL)
1614 		for (x=x1; x<=x2; x++)
1615 			if (bmap[(y-dy)/CELL][x/CELL]==bm)
1616 				if (!FloodWalls(x, y-dy, wall, bm))
1617 					return 0;
1618 	if (y<YRES-CELL)
1619 		for (x=x1; x<=x2; x++)
1620 			if (bmap[(y+dy)/CELL][x/CELL]==bm)
1621 				if (!FloodWalls(x, y+dy, wall, bm))
1622 					return 0;
1623 	return 1;
1624 }
1625 
CreateParts(int positionX,int positionY,int c,Brush * cBrush,int flags)1626 int Simulation::CreateParts(int positionX, int positionY, int c, Brush * cBrush, int flags)
1627 {
1628 	if (flags == -1)
1629 		flags = replaceModeFlags;
1630 	if (cBrush)
1631 	{
1632 		int radiusX = cBrush->GetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y;
1633 		unsigned char *bitmap = cBrush->GetBitmap();
1634 
1635 		// special case for LIGH
1636 		if (c == PT_LIGH)
1637 		{
1638 			if (currentTick < lightningRecreate)
1639 				return 1;
1640 			int newlife = radiusX + radiusY;
1641 			if (newlife > 55)
1642 				newlife = 55;
1643 			c = PMAP(newlife, c);
1644 			lightningRecreate = currentTick+newlife/4;
1645 			return CreatePartFlags(positionX, positionY, c, flags);
1646 		}
1647 		else if (c == PT_TESC)
1648 		{
1649 			int newtmp = (radiusX*4+radiusY*4+7);
1650 			if (newtmp > 300)
1651 				newtmp = 300;
1652 			c = PMAP(newtmp, c);
1653 		}
1654 
1655 		for (int y = sizeY-1; y >=0; y--)
1656 		{
1657 			for (int x = 0; x < sizeX; x++)
1658 			{
1659 				if (bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES))
1660 				{
1661 					CreatePartFlags(positionX+(x-radiusX), positionY+(y-radiusY), c, flags);
1662 				}
1663 			}
1664 		}
1665 	}
1666 	return 0;
1667 }
1668 
CreateParts(int x,int y,int rx,int ry,int c,int flags)1669 int Simulation::CreateParts(int x, int y, int rx, int ry, int c, int flags)
1670 {
1671 	bool created = false;
1672 
1673 	if (flags == -1)
1674 		flags = replaceModeFlags;
1675 
1676 	// special case for LIGH
1677 	if (c == PT_LIGH)
1678 	{
1679 		if (currentTick < lightningRecreate)
1680 			return 1;
1681 		int newlife = rx + ry;
1682 		if (newlife > 55)
1683 			newlife = 55;
1684 		c = PMAP(newlife, c);
1685 		lightningRecreate = currentTick+newlife/4;
1686 		rx = ry = 0;
1687 	}
1688 	else if (c == PT_TESC)
1689 	{
1690 		int newtmp = (rx*4+ry*4+7);
1691 		if (newtmp > 300)
1692 			newtmp = 300;
1693 		c = PMAP(newtmp, c);
1694 	}
1695 
1696 	for (int j = -ry; j <= ry; j++)
1697 		for (int i = -rx; i <= rx; i++)
1698 			if (CreatePartFlags(x+i, y+j, c, flags))
1699 				created = true;
1700 	return !created;
1701 }
1702 
CreateLine(int x1,int y1,int x2,int y2,int c,Brush * cBrush,int flags)1703 void Simulation::CreateLine(int x1, int y1, int x2, int y2, int c, Brush * cBrush, int flags)
1704 {
1705 	int x, y, dx, dy, sy, rx = cBrush->GetRadius().X, ry = cBrush->GetRadius().Y;
1706 	bool reverseXY = abs(y2-y1) > abs(x2-x1);
1707 	float e = 0.0f, de;
1708 	if (reverseXY)
1709 	{
1710 		y = x1;
1711 		x1 = y1;
1712 		y1 = y;
1713 		y = x2;
1714 		x2 = y2;
1715 		y2 = y;
1716 	}
1717 	if (x1 > x2)
1718 	{
1719 		y = x1;
1720 		x1 = x2;
1721 		x2 = y;
1722 		y = y1;
1723 		y1 = y2;
1724 		y2 = y;
1725 	}
1726 	dx = x2 - x1;
1727 	dy = abs(y2 - y1);
1728 	if (dx)
1729 		de = dy/(float)dx;
1730 	else
1731 		de = 0.0f;
1732 	y = y1;
1733 	sy = (y1<y2) ? 1 : -1;
1734 	for (x=x1; x<=x2; x++)
1735 	{
1736 		if (reverseXY)
1737 			CreateParts(y, x, c, cBrush, flags);
1738 		else
1739 			CreateParts(x, y, c, cBrush, flags);
1740 		e += de;
1741 		if (e >= 0.5f)
1742 		{
1743 			y += sy;
1744 			if (!(rx+ry) && ((y1<y2) ? (y<=y2) : (y>=y2)))
1745 			{
1746 				if (reverseXY)
1747 					CreateParts(y, x, c, cBrush, flags);
1748 				else
1749 					CreateParts(x, y, c, cBrush, flags);
1750 			}
1751 			e -= 1.0f;
1752 		}
1753 	}
1754 }
1755 
CreatePartFlags(int x,int y,int c,int flags)1756 int Simulation::CreatePartFlags(int x, int y, int c, int flags)
1757 {
1758 	if (x < 0 || y < 0 || x >= XRES || y >= YRES)
1759 	{
1760 		return 0;
1761 	}
1762 
1763 	if (flags & REPLACE_MODE)
1764 	{
1765 		// if replace whatever and there's something to replace
1766 		// or replace X and there's a non-energy particle on top with type X
1767 		// or replace X and there's an energy particle on top with type X
1768 		if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) ||
1769 			(!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) ||
1770 			(photons[y][x] && TYP(photons[y][x]) == replaceModeSelected))
1771 		{
1772 			if (c)
1773 				create_part(photons[y][x] ? ID(photons[y][x]) : ID(pmap[y][x]), x, y, TYP(c));
1774 			else
1775 				delete_part(x, y);
1776 		}
1777 		return 0;
1778 	}
1779 	else if (!c)
1780 	{
1781 		delete_part(x, y);
1782 		return 0;
1783 	}
1784 	else if (flags & SPECIFIC_DELETE)
1785 	{
1786 		// if delete whatever and there's something to delete
1787 		// or delete X and there's a non-energy particle on top with type X
1788 		// or delete X and there's an energy particle on top with type X
1789 		if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) ||
1790 			(!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) ||
1791 			(photons[y][x] && TYP(photons[y][x]) == replaceModeSelected))
1792 		{
1793 			delete_part(x, y);
1794 		}
1795 		return 0;
1796 	}
1797 	else
1798 	{
1799 		if (create_part(-2, x, y, TYP(c), ID(c)) == -1)
1800 		{
1801 			return 1;
1802 		}
1803 		return 0;
1804 	}
1805 
1806 	// I'm sure at least one compiler exists that would complain if this wasn't here
1807 	return 0;
1808 }
1809 
1810 //Now simply creates a 0 pixel radius line without all the complicated flags / other checks
CreateLine(int x1,int y1,int x2,int y2,int c)1811 void Simulation::CreateLine(int x1, int y1, int x2, int y2, int c)
1812 {
1813 	bool reverseXY = abs(y2-y1) > abs(x2-x1);
1814 	int x, y, dx, dy, sy;
1815 	float e, de;
1816 	int v = ID(c);
1817 	c = TYP(c);
1818 	if (reverseXY)
1819 	{
1820 		y = x1;
1821 		x1 = y1;
1822 		y1 = y;
1823 		y = x2;
1824 		x2 = y2;
1825 		y2 = y;
1826 	}
1827 	if (x1 > x2)
1828 	{
1829 		y = x1;
1830 		x1 = x2;
1831 		x2 = y;
1832 		y = y1;
1833 		y1 = y2;
1834 		y2 = y;
1835 	}
1836 	dx = x2 - x1;
1837 	dy = abs(y2 - y1);
1838 	e = 0.0f;
1839 	if (dx)
1840 		de = dy/(float)dx;
1841 	else
1842 		de = 0.0f;
1843 	y = y1;
1844 	sy = (y1<y2) ? 1 : -1;
1845 	for (x=x1; x<=x2; x++)
1846 	{
1847 		if (reverseXY)
1848 			create_part(-1, y, x, c, v);
1849 		else
1850 			create_part(-1, x, y, c, v);
1851 		e += de;
1852 		if (e >= 0.5f)
1853 		{
1854 			y += sy;
1855 			if ((y1<y2) ? (y<=y2) : (y>=y2))
1856 			{
1857 				if (reverseXY)
1858 					create_part(-1, y, x, c, v);
1859 				else
1860 					create_part(-1, x, y, c, v);
1861 			}
1862 			e -= 1.0f;
1863 		}
1864 	}
1865 }
1866 
CreateBox(int x1,int y1,int x2,int y2,int c,int flags)1867 void Simulation::CreateBox(int x1, int y1, int x2, int y2, int c, int flags)
1868 {
1869 	int i, j;
1870 	if (x1>x2)
1871 	{
1872 		i = x2;
1873 		x2 = x1;
1874 		x1 = i;
1875 	}
1876 	if (y1>y2)
1877 	{
1878 		j = y2;
1879 		y2 = y1;
1880 		y1 = j;
1881 	}
1882 	for (j=y2; j>=y1; j--)
1883 		for (i=x1; i<=x2; i++)
1884 			CreateParts(i, j, 0, 0, c, flags);
1885 }
1886 
FloodParts(int x,int y,int fullc,int cm,int flags)1887 int Simulation::FloodParts(int x, int y, int fullc, int cm, int flags)
1888 {
1889 	int c = TYP(fullc);
1890 	int x1, x2, dy = (c<PT_NUM)?1:CELL;
1891 	int coord_stack_limit = XRES*YRES;
1892 	unsigned short (*coord_stack)[2];
1893 	int coord_stack_size = 0;
1894 	int created_something = 0;
1895 
1896 	if (cm==-1)
1897 	{
1898 		//if initial flood point is out of bounds, do nothing
1899 		if (c != 0 && (x < CELL || x >= XRES-CELL || y < CELL || y >= YRES-CELL || c == PT_SPRK))
1900 			return 1;
1901 		else if (x < 0 || x >= XRES || y < 0 || y >= YRES)
1902 			return 1;
1903 		if (c == 0)
1904 		{
1905 			cm = TYP(pmap[y][x]);
1906 			if (!cm)
1907 			{
1908 				cm = TYP(photons[y][x]);
1909 				if (!cm)
1910 				{
1911 					if (bmap[y/CELL][x/CELL])
1912 						return FloodWalls(x, y, WL_ERASE, -1);
1913 					else
1914 						return -1;
1915 				}
1916 			}
1917 		}
1918 		else
1919 			cm = 0;
1920 	}
1921 	if (c != 0 && IsWallBlocking(x, y, c))
1922 		return 1;
1923 
1924 	if (!FloodFillPmapCheck(x, y, cm))
1925 		return 1;
1926 
1927 	coord_stack = (short unsigned int (*)[2])malloc(sizeof(unsigned short)*2*coord_stack_limit);
1928 	coord_stack[coord_stack_size][0] = x;
1929 	coord_stack[coord_stack_size][1] = y;
1930 	coord_stack_size++;
1931 
1932 	do
1933 	{
1934 		coord_stack_size--;
1935 		x = coord_stack[coord_stack_size][0];
1936 		y = coord_stack[coord_stack_size][1];
1937 		x1 = x2 = x;
1938 		// go left as far as possible
1939 		while (c?x1>CELL:x1>0)
1940 		{
1941 			if (!FloodFillPmapCheck(x1-1, y, cm) || (c != 0 && IsWallBlocking(x1-1, y, c)))
1942 			{
1943 				break;
1944 			}
1945 			x1--;
1946 		}
1947 		// go right as far as possible
1948 		while (c?x2<XRES-CELL-1:x2<XRES-1)
1949 		{
1950 			if (!FloodFillPmapCheck(x2+1, y, cm) || (c != 0 && IsWallBlocking(x2+1, y, c)))
1951 			{
1952 				break;
1953 			}
1954 			x2++;
1955 		}
1956 		// fill span
1957 		for (x=x1; x<=x2; x++)
1958 		{
1959 			if (!fullc)
1960 			{
1961 				if (elements[cm].Properties&TYPE_ENERGY)
1962 				{
1963 					if (photons[y][x])
1964 					{
1965 						kill_part(ID(photons[y][x]));
1966 						created_something = 1;
1967 					}
1968 				}
1969 				else if (pmap[y][x])
1970 				{
1971 					kill_part(ID(pmap[y][x]));
1972 					created_something = 1;
1973 				}
1974 			}
1975 			else if (CreateParts(x, y, 0, 0, fullc, flags))
1976 				created_something = 1;
1977 		}
1978 
1979 		if (c?y>=CELL+dy:y>=dy)
1980 			for (x=x1; x<=x2; x++)
1981 				if (FloodFillPmapCheck(x, y-dy, cm) && (c == 0 || !IsWallBlocking(x, y-dy, c)))
1982 				{
1983 					coord_stack[coord_stack_size][0] = x;
1984 					coord_stack[coord_stack_size][1] = y-dy;
1985 					coord_stack_size++;
1986 					if (coord_stack_size>=coord_stack_limit)
1987 					{
1988 						free(coord_stack);
1989 						return -1;
1990 					}
1991 				}
1992 
1993 		if (c?y<YRES-CELL-dy:y<YRES-dy)
1994 			for (x=x1; x<=x2; x++)
1995 				if (FloodFillPmapCheck(x, y+dy, cm) && (c == 0 || !IsWallBlocking(x, y+dy, c)))
1996 				{
1997 					coord_stack[coord_stack_size][0] = x;
1998 					coord_stack[coord_stack_size][1] = y+dy;
1999 					coord_stack_size++;
2000 					if (coord_stack_size>=coord_stack_limit)
2001 					{
2002 						free(coord_stack);
2003 						return -1;
2004 					}
2005 				}
2006 	} while (coord_stack_size>0);
2007 	free(coord_stack);
2008 	return created_something;
2009 }
2010 
orbitalparts_get(int block1,int block2,int resblock1[],int resblock2[])2011 void Simulation::orbitalparts_get(int block1, int block2, int resblock1[], int resblock2[])
2012 {
2013 	resblock1[0] = (block1&0x000000FF);
2014 	resblock1[1] = (block1&0x0000FF00)>>8;
2015 	resblock1[2] = (block1&0x00FF0000)>>16;
2016 	resblock1[3] = (block1&0xFF000000)>>24;
2017 
2018 	resblock2[0] = (block2&0x000000FF);
2019 	resblock2[1] = (block2&0x0000FF00)>>8;
2020 	resblock2[2] = (block2&0x00FF0000)>>16;
2021 	resblock2[3] = (block2&0xFF000000)>>24;
2022 }
2023 
orbitalparts_set(int * block1,int * block2,int resblock1[],int resblock2[])2024 void Simulation::orbitalparts_set(int *block1, int *block2, int resblock1[], int resblock2[])
2025 {
2026 	int block1tmp = 0;
2027 	int block2tmp = 0;
2028 
2029 	block1tmp = (resblock1[0]&0xFF);
2030 	block1tmp |= (resblock1[1]&0xFF)<<8;
2031 	block1tmp |= (resblock1[2]&0xFF)<<16;
2032 	block1tmp |= (resblock1[3]&0xFF)<<24;
2033 
2034 	block2tmp = (resblock2[0]&0xFF);
2035 	block2tmp |= (resblock2[1]&0xFF)<<8;
2036 	block2tmp |= (resblock2[2]&0xFF)<<16;
2037 	block2tmp |= (resblock2[3]&0xFF)<<24;
2038 
2039 	*block1 = block1tmp;
2040 	*block2 = block2tmp;
2041 }
2042 
is_wire(int x,int y)2043 inline int Simulation::is_wire(int x, int y)
2044 {
2045 	return bmap[y][x]==WL_DETECT || bmap[y][x]==WL_EWALL || bmap[y][x]==WL_ALLOWLIQUID || bmap[y][x]==WL_WALLELEC || bmap[y][x]==WL_ALLOWALLELEC || bmap[y][x]==WL_EHOLE || bmap[y][x]==WL_STASIS;
2046 }
2047 
is_wire_off(int x,int y)2048 inline int Simulation::is_wire_off(int x, int y)
2049 {
2050 	return (bmap[y][x]==WL_DETECT || bmap[y][x]==WL_EWALL || bmap[y][x]==WL_ALLOWLIQUID || bmap[y][x]==WL_WALLELEC || bmap[y][x]==WL_ALLOWALLELEC || bmap[y][x]==WL_EHOLE || bmap[y][x]==WL_STASIS) && emap[y][x]<8;
2051 }
2052 
2053 // implement __builtin_ctz and __builtin_clz on msvc
2054 #ifdef _MSC_VER
msvc_ctz(unsigned a)2055 unsigned msvc_ctz(unsigned a)
2056 {
2057 	unsigned long i;
2058 	_BitScanForward(&i, a);
2059 	return i;
2060 }
2061 
msvc_clz(unsigned a)2062 unsigned msvc_clz(unsigned a)
2063 {
2064 	unsigned long i;
2065 	_BitScanReverse(&i, a);
2066 	return 31 - i;
2067 }
2068 
2069 #define __builtin_ctz msvc_ctz
2070 #define __builtin_clz msvc_clz
2071 #endif
2072 
get_wavelength_bin(int * wm)2073 int Simulation::get_wavelength_bin(int *wm)
2074 {
2075 	int i, w0, wM, r;
2076 
2077 	if (!(*wm & 0x3FFFFFFF))
2078 		return -1;
2079 
2080 #if defined(__GNUC__) || defined(_MSVC_VER)
2081 	w0 = __builtin_ctz(*wm | 0xC0000000);
2082 	wM = 31 - __builtin_clz(*wm & 0x3FFFFFFF);
2083 #else
2084 	w0 = 30;
2085 	wM = 0;
2086 	for (i = 0; i < 30; i++)
2087 		if (*wm & (1<<i))
2088 		{
2089 			if (i < w0)
2090 				w0 = i;
2091 			if (i > wM)
2092 				wM = i;
2093 		}
2094 #endif
2095 
2096 	if (wM - w0 < 5)
2097 		return wM + w0;
2098 
2099 	r = RNG::Ref().gen();
2100 	i = (r >> 1) % (wM-w0-4);
2101 	i += w0;
2102 
2103 	if (r & 1)
2104 	{
2105 		*wm &= 0x1F << i;
2106 		return (i + 2) * 2;
2107 	}
2108 	else
2109 	{
2110 		*wm &= 0xF << i;
2111 		return (i + 2) * 2 - 1;
2112 	}
2113 }
2114 
set_emap(int x,int y)2115 void Simulation::set_emap(int x, int y)
2116 {
2117 	int x1, x2;
2118 
2119 	if (!is_wire_off(x, y))
2120 		return;
2121 
2122 	// go left as far as possible
2123 	x1 = x2 = x;
2124 	while (x1>0)
2125 	{
2126 		if (!is_wire_off(x1-1, y))
2127 			break;
2128 		x1--;
2129 	}
2130 	while (x2<XRES/CELL-1)
2131 	{
2132 		if (!is_wire_off(x2+1, y))
2133 			break;
2134 		x2++;
2135 	}
2136 
2137 	// fill span
2138 	for (x=x1; x<=x2; x++)
2139 		emap[y][x] = 16;
2140 
2141 	// fill children
2142 
2143 	if (y>1 && x1==x2 &&
2144 	        is_wire(x1-1, y-1) && is_wire(x1, y-1) && is_wire(x1+1, y-1) &&
2145 	        !is_wire(x1-1, y-2) && is_wire(x1, y-2) && !is_wire(x1+1, y-2))
2146 		set_emap(x1, y-2);
2147 	else if (y>0)
2148 		for (x=x1; x<=x2; x++)
2149 			if (is_wire_off(x, y-1))
2150 			{
2151 				if (x==x1 || x==x2 || y>=YRES/CELL-1 ||
2152 				        is_wire(x-1, y-1) || is_wire(x+1, y-1) ||
2153 				        is_wire(x-1, y+1) || !is_wire(x, y+1) || is_wire(x+1, y+1))
2154 					set_emap(x, y-1);
2155 			}
2156 
2157 	if (y<YRES/CELL-2 && x1==x2 &&
2158 	        is_wire(x1-1, y+1) && is_wire(x1, y+1) && is_wire(x1+1, y+1) &&
2159 	        !is_wire(x1-1, y+2) && is_wire(x1, y+2) && !is_wire(x1+1, y+2))
2160 		set_emap(x1, y+2);
2161 	else if (y<YRES/CELL-1)
2162 		for (x=x1; x<=x2; x++)
2163 			if (is_wire_off(x, y+1))
2164 			{
2165 				if (x==x1 || x==x2 || y<0 ||
2166 				        is_wire(x-1, y+1) || is_wire(x+1, y+1) ||
2167 				        is_wire(x-1, y-1) || !is_wire(x, y-1) || is_wire(x+1, y-1))
2168 					set_emap(x, y+1);
2169 			}
2170 }
2171 
parts_avg(int ci,int ni,int t)2172 int Simulation::parts_avg(int ci, int ni,int t)
2173 {
2174 	if (t==PT_INSL)//to keep electronics working
2175 	{
2176 		int pmr = pmap[((int)(parts[ci].y+0.5f) + (int)(parts[ni].y+0.5f))/2][((int)(parts[ci].x+0.5f) + (int)(parts[ni].x+0.5f))/2];
2177 		if (pmr)
2178 			return parts[ID(pmr)].type;
2179 		else
2180 			return PT_NONE;
2181 	}
2182 	else
2183 	{
2184 		int pmr2 = pmap[(int)((parts[ci].y + parts[ni].y)/2+0.5f)][(int)((parts[ci].x + parts[ni].x)/2+0.5f)];//seems to be more accurate.
2185 		if (pmr2)
2186 		{
2187 			if (parts[ID(pmr2)].type==t)
2188 				return t;
2189 		}
2190 		else
2191 			return PT_NONE;
2192 	}
2193 	return PT_NONE;
2194 }
2195 
2196 
2197 // unused function
create_arc(int sx,int sy,int dx,int dy,int midpoints,int variance,int type,int flags)2198 void Simulation::create_arc(int sx, int sy, int dx, int dy, int midpoints, int variance, int type, int flags)
2199 {
2200 	int i;
2201 	float xint, yint;
2202 	int *xmid, *ymid;
2203 	int voffset = variance/2;
2204 	xmid = (int *)calloc(midpoints + 2, sizeof(int));
2205 	ymid = (int *)calloc(midpoints + 2, sizeof(int));
2206 	xint = (float)(dx-sx)/(float)(midpoints+1.0f);
2207 	yint = (float)(dy-sy)/(float)(midpoints+1.0f);
2208 	xmid[0] = sx;
2209 	xmid[midpoints+1] = dx;
2210 	ymid[0] = sy;
2211 	ymid[midpoints+1] = dy;
2212 
2213 	for(i = 1; i <= midpoints; i++)
2214 	{
2215 		ymid[i] = ymid[i-1]+yint;
2216 		xmid[i] = xmid[i-1]+xint;
2217 	}
2218 
2219 	for(i = 0; i <= midpoints; i++)
2220 	{
2221 		if(i!=midpoints)
2222 		{
2223 			xmid[i+1] += RNG::Ref().between(0, variance - 1) - voffset;
2224 			ymid[i+1] += RNG::Ref().between(0, variance - 1) - voffset;
2225 		}
2226 		CreateLine(xmid[i], ymid[i], xmid[i+1], ymid[i+1], type);
2227 	}
2228 	free(xmid);
2229 	free(ymid);
2230 }
2231 
clear_sim(void)2232 void Simulation::clear_sim(void)
2233 {
2234 	debug_currentParticle = 0;
2235 	emp_decor = 0;
2236 	emp_trigger_count = 0;
2237 	signs.clear();
2238 	memset(bmap, 0, sizeof(bmap));
2239 	memset(emap, 0, sizeof(emap));
2240 	memset(parts, 0, sizeof(Particle)*NPART);
2241 	for (int i = 0; i < NPART-1; i++)
2242 		parts[i].life = i+1;
2243 	parts[NPART-1].life = -1;
2244 	pfree = 0;
2245 	parts_lastActiveIndex = 0;
2246 	memset(pmap, 0, sizeof(pmap));
2247 	memset(fvx, 0, sizeof(fvx));
2248 	memset(fvy, 0, sizeof(fvy));
2249 	memset(photons, 0, sizeof(photons));
2250 	memset(wireless, 0, sizeof(wireless));
2251 	memset(gol2, 0, sizeof(gol2));
2252 	memset(portalp, 0, sizeof(portalp));
2253 	memset(fighters, 0, sizeof(fighters));
2254 	std::fill(elementCount, elementCount+PT_NUM, 0);
2255 	elementRecount = true;
2256 	fighcount = 0;
2257 	player.spwn = 0;
2258 	player.spawnID = -1;
2259 	player.rocketBoots = false;
2260 	player.fan = false;
2261 	player2.spwn = 0;
2262 	player2.spawnID = -1;
2263 	player2.rocketBoots = false;
2264 	player2.fan = false;
2265 	//memset(pers_bg, 0, WINDOWW*YRES*PIXELSIZE);
2266 	//memset(fire_r, 0, sizeof(fire_r));
2267 	//memset(fire_g, 0, sizeof(fire_g));
2268 	//memset(fire_b, 0, sizeof(fire_b));
2269 	//if(gravmask)
2270 		//memset(gravmask, 0xFFFFFFFF, (XRES/CELL)*(YRES/CELL)*sizeof(unsigned));
2271 	if(grav)
2272 		grav->Clear();
2273 	if(air)
2274 	{
2275 		air->Clear();
2276 		air->ClearAirH();
2277 	}
2278 	SetEdgeMode(edgeMode);
2279 }
2280 
IsWallBlocking(int x,int y,int type)2281 bool Simulation::IsWallBlocking(int x, int y, int type)
2282 {
2283 	if (bmap[y/CELL][x/CELL])
2284 	{
2285 		int wall = bmap[y/CELL][x/CELL];
2286 		if (wall == WL_ALLOWGAS && !(elements[type].Properties&TYPE_GAS))
2287 			return true;
2288 		else if (wall == WL_ALLOWENERGY && !(elements[type].Properties&TYPE_ENERGY))
2289 			return true;
2290 		else if (wall == WL_ALLOWLIQUID && !(elements[type].Properties&TYPE_LIQUID))
2291 			return true;
2292 		else if (wall == WL_ALLOWPOWDER && !(elements[type].Properties&TYPE_PART))
2293 			return true;
2294 		else if (wall == WL_ALLOWAIR || wall == WL_WALL || wall == WL_WALLELEC)
2295 			return true;
2296 		else if (wall == WL_EWALL && !emap[y/CELL][x/CELL])
2297 			return true;
2298 	}
2299 	return false;
2300 }
2301 
init_can_move()2302 void Simulation::init_can_move()
2303 {
2304 	int movingType, destinationType;
2305 	// can_move[moving type][type at destination]
2306 	//  0 = No move/Bounce
2307 	//  1 = Swap
2308 	//  2 = Both particles occupy the same space.
2309 	//  3 = Varies, go run some extra checks
2310 
2311 	//particles that don't exist shouldn't move...
2312 	for (destinationType = 0; destinationType < PT_NUM; destinationType++)
2313 		can_move[0][destinationType] = 0;
2314 
2315 	//initialize everything else to swapping by default
2316 	for (movingType = 1; movingType < PT_NUM; movingType++)
2317 		for (destinationType = 0; destinationType < PT_NUM; destinationType++)
2318 			can_move[movingType][destinationType] = 1;
2319 
2320 	//photons go through everything by default
2321 	for (destinationType = 1; destinationType < PT_NUM; destinationType++)
2322 		can_move[PT_PHOT][destinationType] = 2;
2323 
2324 	for (movingType = 1; movingType < PT_NUM; movingType++)
2325 	{
2326 		for (destinationType = 1; destinationType < PT_NUM; destinationType++)
2327 		{
2328 			//weight check, also prevents particles of same type displacing each other
2329 			if (elements[movingType].Weight <= elements[destinationType].Weight || destinationType == PT_GEL)
2330 				can_move[movingType][destinationType] = 0;
2331 
2332 			//other checks for NEUT and energy particles
2333 			if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTPASS))
2334 				can_move[movingType][destinationType] = 2;
2335 			if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTABSORB))
2336 				can_move[movingType][destinationType] = 1;
2337 			if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTPENETRATE))
2338 				can_move[movingType][destinationType] = 1;
2339 			if (destinationType == PT_NEUT && (elements[movingType].Properties&PROP_NEUTPENETRATE))
2340 				can_move[movingType][destinationType] = 0;
2341 			if ((elements[movingType].Properties&TYPE_ENERGY) && (elements[destinationType].Properties&TYPE_ENERGY))
2342 				can_move[movingType][destinationType] = 2;
2343 		}
2344 	}
2345 	for (destinationType = 0; destinationType < PT_NUM; destinationType++)
2346 	{
2347 		//set what stickmen can move through
2348 		int stkm_move = 0;
2349 		if (elements[destinationType].Properties & (TYPE_LIQUID | TYPE_GAS))
2350 			stkm_move = 2;
2351 		if (!destinationType || destinationType == PT_PRTO || destinationType == PT_SPAWN || destinationType == PT_SPAWN2)
2352 			stkm_move = 2;
2353 		can_move[PT_STKM][destinationType] = stkm_move;
2354 		can_move[PT_STKM2][destinationType] = stkm_move;
2355 		can_move[PT_FIGH][destinationType] = stkm_move;
2356 
2357 		//spark shouldn't move
2358 		can_move[PT_SPRK][destinationType] = 0;
2359 	}
2360 	for (movingType = 1; movingType < PT_NUM; movingType++)
2361 	{
2362 		//everything "swaps" with VACU and BHOL to make them eat things
2363 		can_move[movingType][PT_BHOL] = 1;
2364 		can_move[movingType][PT_NBHL] = 1;
2365 		//nothing goes through stickmen
2366 		can_move[movingType][PT_STKM] = 0;
2367 		can_move[movingType][PT_STKM2] = 0;
2368 		can_move[movingType][PT_FIGH] = 0;
2369 		//INVS behaviour varies with pressure
2370 		can_move[movingType][PT_INVIS] = 3;
2371 		//stop CNCT from being displaced by other particles
2372 		can_move[movingType][PT_CNCT] = 0;
2373 		//VOID and PVOD behaviour varies with powered state and ctype
2374 		can_move[movingType][PT_PVOD] = 3;
2375 		can_move[movingType][PT_VOID] = 3;
2376 		//nothing moves through EMBR (not sure why, but it's killed when it touches anything)
2377 		can_move[movingType][PT_EMBR] = 0;
2378 		can_move[PT_EMBR][movingType] = 0;
2379 		//Energy particles move through VIBR and BVBR, so it can absorb them
2380 		if (elements[movingType].Properties & TYPE_ENERGY)
2381 		{
2382 			can_move[movingType][PT_VIBR] = 1;
2383 			can_move[movingType][PT_BVBR] = 1;
2384 		}
2385 
2386 		//SAWD cannot be displaced by other powders
2387 		if (elements[movingType].Properties & TYPE_PART)
2388 			can_move[movingType][PT_SAWD] = 0;
2389 	}
2390 	//a list of lots of things PHOT can move through
2391 	// TODO: replace with property
2392 	for (destinationType = 0; destinationType < PT_NUM; destinationType++)
2393 	{
2394 		if (destinationType == PT_GLAS || destinationType == PT_PHOT || destinationType == PT_FILT || destinationType == PT_INVIS
2395 		 || destinationType == PT_CLNE || destinationType == PT_PCLN || destinationType == PT_BCLN || destinationType == PT_PBCN
2396 		 || destinationType == PT_WATR || destinationType == PT_DSTW || destinationType == PT_SLTW || destinationType == PT_GLOW
2397 		 || destinationType == PT_ISOZ || destinationType == PT_ISZS || destinationType == PT_QRTZ || destinationType == PT_PQRT
2398 		 || destinationType == PT_H2   || destinationType == PT_BGLA || destinationType == PT_C5)
2399 			can_move[PT_PHOT][destinationType] = 2;
2400 		if (destinationType != PT_DMND && destinationType != PT_INSL && destinationType != PT_VOID && destinationType != PT_PVOD && destinationType != PT_VIBR && destinationType != PT_BVBR && destinationType != PT_PRTI && destinationType != PT_PRTO)
2401 		{
2402 			can_move[PT_PROT][destinationType] = 2;
2403 			can_move[PT_GRVT][destinationType] = 2;
2404 		}
2405 	}
2406 
2407 	//other special cases that weren't covered above
2408 	can_move[PT_DEST][PT_DMND] = 0;
2409 	can_move[PT_DEST][PT_CLNE] = 0;
2410 	can_move[PT_DEST][PT_PCLN] = 0;
2411 	can_move[PT_DEST][PT_BCLN] = 0;
2412 	can_move[PT_DEST][PT_PBCN] = 0;
2413 
2414 	can_move[PT_NEUT][PT_INVIS] = 2;
2415 	can_move[PT_ELEC][PT_LCRY] = 2;
2416 	can_move[PT_ELEC][PT_EXOT] = 2;
2417 	can_move[PT_ELEC][PT_GLOW] = 2;
2418 	can_move[PT_PHOT][PT_LCRY] = 3; //varies according to LCRY life
2419 	can_move[PT_PHOT][PT_GPMP] = 3;
2420 
2421 	can_move[PT_PHOT][PT_BIZR] = 2;
2422 	can_move[PT_ELEC][PT_BIZR] = 2;
2423 	can_move[PT_PHOT][PT_BIZRG] = 2;
2424 	can_move[PT_ELEC][PT_BIZRG] = 2;
2425 	can_move[PT_PHOT][PT_BIZRS] = 2;
2426 	can_move[PT_ELEC][PT_BIZRS] = 2;
2427 	can_move[PT_BIZR][PT_FILT] = 2;
2428 	can_move[PT_BIZRG][PT_FILT] = 2;
2429 
2430 	can_move[PT_ANAR][PT_WHOL] = 1; //WHOL eats ANAR
2431 	can_move[PT_ANAR][PT_NWHL] = 1;
2432 	can_move[PT_ELEC][PT_DEUT] = 1;
2433 	can_move[PT_THDR][PT_THDR] = 2;
2434 	can_move[PT_EMBR][PT_EMBR] = 2;
2435 	can_move[PT_TRON][PT_SWCH] = 3;
2436 }
2437 
2438 /*
2439    RETURN-value explanation
2440 1 = Swap
2441 0 = No move/Bounce
2442 2 = Both particles occupy the same space.
2443  */
eval_move(int pt,int nx,int ny,unsigned * rr)2444 int Simulation::eval_move(int pt, int nx, int ny, unsigned *rr)
2445 {
2446 	unsigned r;
2447 	int result;
2448 
2449 	if (nx<0 || ny<0 || nx>=XRES || ny>=YRES)
2450 		return 0;
2451 
2452 	r = pmap[ny][nx];
2453 	if (r)
2454 		r = (r&~PMAPMASK) | parts[ID(r)].type;
2455 	if (rr)
2456 		*rr = r;
2457 	if (pt>=PT_NUM || TYP(r)>=PT_NUM)
2458 		return 0;
2459 	result = can_move[pt][TYP(r)];
2460 	if (result == 3)
2461 	{
2462 		switch (TYP(r))
2463 		{
2464 		case PT_LCRY:
2465 			if (pt==PT_PHOT)
2466 				result = (parts[ID(r)].life > 5)? 2 : 0;
2467 			break;
2468 		case PT_GPMP:
2469 			if (pt == PT_PHOT)
2470 				result = (parts[ID(r)].life < 10) ? 2 : 0;
2471 			break;
2472 		case PT_INVIS:
2473 		{
2474 			float pressureResistance = 0.0f;
2475 			if (parts[ID(r)].tmp > 0)
2476 				pressureResistance = (float)parts[ID(r)].tmp;
2477 			else
2478 				pressureResistance = 4.0f;
2479 
2480 			if (pv[ny/CELL][nx/CELL] < -pressureResistance || pv[ny/CELL][nx/CELL] > pressureResistance)
2481 				result = 2;
2482 			else
2483 				result = 0;
2484 			break;
2485 		}
2486 		case PT_PVOD:
2487 			if (parts[ID(r)].life == 10)
2488 			{
2489 				if (!parts[ID(r)].ctype || (parts[ID(r)].ctype==pt)!=(parts[ID(r)].tmp&1))
2490 					result = 1;
2491 				else
2492 					result = 0;
2493 			}
2494 			else result = 0;
2495 			break;
2496 		case PT_VOID:
2497 			if (!parts[ID(r)].ctype || (parts[ID(r)].ctype==pt)!=(parts[ID(r)].tmp&1))
2498 				result = 1;
2499 			else
2500 				result = 0;
2501 			break;
2502 		case PT_SWCH:
2503 			if (pt == PT_TRON)
2504 			{
2505 				if (parts[ID(r)].life >= 10)
2506 					return 2;
2507 				else
2508 					return 0;
2509 			}
2510 			break;
2511 		default:
2512 			// This should never happen
2513 			// If it were to happen, try_move would interpret a 3 as a 1
2514 			result =  1;
2515 		}
2516 	}
2517 	if (bmap[ny/CELL][nx/CELL])
2518 	{
2519 		if (IsWallBlocking(nx, ny, pt))
2520 			return 0;
2521 		if (bmap[ny/CELL][nx/CELL]==WL_EHOLE && !emap[ny/CELL][nx/CELL] && !(elements[pt].Properties&TYPE_SOLID) && !(elements[TYP(r)].Properties&TYPE_SOLID))
2522 			return 2;
2523 	}
2524 	return result;
2525 }
2526 
try_move(int i,int x,int y,int nx,int ny)2527 int Simulation::try_move(int i, int x, int y, int nx, int ny)
2528 {
2529 	unsigned r = 0, e;
2530 
2531 	if (x==nx && y==ny)
2532 		return 1;
2533 	if (nx<0 || ny<0 || nx>=XRES || ny>=YRES)
2534 		return 1;
2535 
2536 	e = eval_move(parts[i].type, nx, ny, &r);
2537 
2538 	/* half-silvered mirror */
2539 	if (!e && parts[i].type==PT_PHOT && ((TYP(r)==PT_BMTL && RNG::Ref().chance(1, 2)) || TYP(pmap[y][x])==PT_BMTL))
2540 		e = 2;
2541 
2542 	if (!e) //if no movement
2543 	{
2544 		int rt = TYP(r);
2545 		if (rt == PT_WOOD)
2546 		{
2547 			float vel = std::sqrt(std::pow(parts[i].vx, 2) + std::pow(parts[i].vy, 2));
2548 			if (vel > 5)
2549 				part_change_type(ID(r), nx, ny, PT_SAWD);
2550 		}
2551 		if (!(elements[parts[i].type].Properties & TYPE_ENERGY))
2552 			return 0;
2553 		if (!legacy_enable && parts[i].type==PT_PHOT && r)//PHOT heat conduction
2554 		{
2555 			if (rt == PT_COAL || rt == PT_BCOL)
2556 				parts[ID(r)].temp = parts[i].temp;
2557 
2558 			if (rt < PT_NUM && elements[rt].HeatConduct && (rt!=PT_HSWC||parts[ID(r)].life==10) && rt!=PT_FILT)
2559 				parts[i].temp = parts[ID(r)].temp = restrict_flt((parts[ID(r)].temp+parts[i].temp)/2, MIN_TEMP, MAX_TEMP);
2560 		}
2561 		else if ((parts[i].type==PT_NEUT || parts[i].type==PT_ELEC) && (rt==PT_CLNE || rt==PT_PCLN || rt==PT_BCLN || rt==PT_PBCN))
2562 		{
2563 			if (!parts[ID(r)].ctype)
2564 				parts[ID(r)].ctype = parts[i].type;
2565 		}
2566 		if (rt==PT_PRTI && (elements[parts[i].type].Properties & TYPE_ENERGY))
2567 		{
2568 			int nnx, count;
2569 			for (count=0; count<8; count++)
2570 			{
2571 				if (isign(x-nx)==isign(portal_rx[count]) && isign(y-ny)==isign(portal_ry[count]))
2572 					break;
2573 			}
2574 			count = count%8;
2575 			parts[ID(r)].tmp = (int)((parts[ID(r)].temp-73.15f)/100+1);
2576 			if (parts[ID(r)].tmp>=CHANNELS) parts[ID(r)].tmp = CHANNELS-1;
2577 			else if (parts[ID(r)].tmp<0) parts[ID(r)].tmp = 0;
2578 			for ( nnx=0; nnx<80; nnx++)
2579 				if (!portalp[parts[ID(r)].tmp][count][nnx].type)
2580 				{
2581 					portalp[parts[ID(r)].tmp][count][nnx] = parts[i];
2582 					parts[i].type=PT_NONE;
2583 					break;
2584 				}
2585 		}
2586 		return 0;
2587 	}
2588 
2589 	int Element_FILT_interactWavelengths(Particle* cpart, int origWl);
2590 	if (e == 2) //if occupy same space
2591 	{
2592 		switch (parts[i].type)
2593 		{
2594 		case PT_PHOT:
2595 		{
2596 			switch (TYP(r))
2597 			{
2598 			case PT_GLOW:
2599 				if (!parts[ID(r)].life && RNG::Ref().chance(29, 30))
2600 				{
2601 					parts[ID(r)].life = 120;
2602 					create_gain_photon(i);
2603 				}
2604 				break;
2605 			case PT_FILT:
2606 				parts[i].ctype = Element_FILT_interactWavelengths(&parts[ID(r)], parts[i].ctype);
2607 				break;
2608 			case PT_C5:
2609 				if (parts[ID(r)].life > 0 && (parts[ID(r)].ctype & parts[i].ctype & 0xFFFFFFC0))
2610 				{
2611 					float vx = ((parts[ID(r)].tmp << 16) >> 16) / 255.0f;
2612 					float vy = (parts[ID(r)].tmp >> 16) / 255.0f;
2613 					float vn = parts[i].vx * parts[i].vx + parts[i].vy * parts[i].vy;
2614 					// if the resulting velocity would be 0, that would cause division by 0 inside the else
2615 					// shoot the photon off at a 90 degree angle instead (probably particle order dependent)
2616 					if (parts[i].vx + vx == 0 && parts[i].vy + vy == 0)
2617 					{
2618 						parts[i].vx = vy;
2619 						parts[i].vy = -vx;
2620 					}
2621 					else
2622 					{
2623 						parts[i].ctype = (parts[ID(r)].ctype & parts[i].ctype) >> 6;
2624 						// add momentum of photons to each other
2625 						parts[i].vx += vx;
2626 						parts[i].vy += vy;
2627 						// normalize velocity to original value
2628 						vn /= parts[i].vx * parts[i].vx + parts[i].vy * parts[i].vy;
2629 						vn = sqrtf(vn);
2630 						parts[i].vx *= vn;
2631 						parts[i].vy *= vn;
2632 					}
2633 					parts[ID(r)].life = 0;
2634 					parts[ID(r)].ctype = 0;
2635 				}
2636 				else if(!parts[ID(r)].ctype && parts[i].ctype & 0xFFFFFFC0)
2637 				{
2638 					parts[ID(r)].life = 1;
2639 					parts[ID(r)].ctype = parts[i].ctype;
2640 					parts[ID(r)].tmp = (0xFFFF & (int)(parts[i].vx * 255.0f)) | (0xFFFF0000 & (int)(parts[i].vy * 16711680.0f));
2641 					parts[ID(r)].tmp2 = (0xFFFF & (int)((parts[i].x - x) * 255.0f)) | (0xFFFF0000 & (int)((parts[i].y - y) * 16711680.0f));
2642 					kill_part(i);
2643 				}
2644 				break;
2645 			case PT_INVIS:
2646 			{
2647 				float pressureResistance = 0.0f;
2648 				if (parts[ID(r)].tmp > 0)
2649 					pressureResistance = (float)parts[ID(r)].tmp;
2650 				else
2651 					pressureResistance = 4.0f;
2652 				if (pv[ny/CELL][nx/CELL] >= -pressureResistance && pv[ny/CELL][nx/CELL] <= pressureResistance)
2653 				{
2654 					part_change_type(i,x,y,PT_NEUT);
2655 					parts[i].ctype = 0;
2656 				}
2657 				break;
2658 			}
2659 			case PT_BIZR:
2660 			case PT_BIZRG:
2661 			case PT_BIZRS:
2662 				part_change_type(i, x, y, PT_ELEC);
2663 				parts[i].ctype = 0;
2664 				break;
2665 			case PT_H2:
2666 				if (!(parts[i].tmp&0x1))
2667 				{
2668 					part_change_type(i, x, y, PT_PROT);
2669 					parts[i].ctype = 0;
2670 					parts[i].tmp2 = 0x1;
2671 
2672 					create_part(ID(r), x, y, PT_ELEC);
2673 					return 1;
2674 				}
2675 				break;
2676 			case PT_GPMP:
2677 				if (parts[ID(r)].life == 0)
2678 				{
2679 					part_change_type(i, x, y, PT_GRVT);
2680 					parts[i].tmp = parts[ID(r)].temp - 273.15f;
2681 				}
2682 				break;
2683 			}
2684 			break;
2685 		}
2686 		case PT_NEUT:
2687 			if (TYP(r) == PT_GLAS || TYP(r) == PT_BGLA)
2688 				if (RNG::Ref().chance(9, 10))
2689 					create_cherenkov_photon(i);
2690 			break;
2691 		case PT_ELEC:
2692 			if (TYP(r) == PT_GLOW)
2693 			{
2694 				part_change_type(i, x, y, PT_PHOT);
2695 				parts[i].ctype = 0x3FFFFFFF;
2696 			}
2697 			break;
2698 		case PT_PROT:
2699 			if (TYP(r) == PT_INVIS)
2700 				part_change_type(i, x, y, PT_NEUT);
2701 			break;
2702 		case PT_BIZR:
2703 		case PT_BIZRG:
2704 			if (TYP(r) == PT_FILT)
2705 				parts[i].ctype = Element_FILT_interactWavelengths(&parts[ID(r)], parts[i].ctype);
2706 			break;
2707 		}
2708 		return 1;
2709 	}
2710 	//else e=1 , we are trying to swap the particles, return 0 no swap/move, 1 is still overlap/move, because the swap takes place later
2711 
2712 	switch (TYP(r))
2713 	{
2714 	case PT_VOID:
2715 	case PT_PVOD:
2716 		// this is where void eats particles
2717 		// void ctype already checked in eval_move
2718 		kill_part(i);
2719 		return 0;
2720 	case PT_BHOL:
2721 	case PT_NBHL:
2722 		// this is where blackhole eats particles
2723 		if (!legacy_enable)
2724 		{
2725 			parts[ID(r)].temp = restrict_flt(parts[ID(r)].temp+parts[i].temp/2, MIN_TEMP, MAX_TEMP);//3.0f;
2726 		}
2727 		kill_part(i);
2728 		return 0;
2729 	case PT_WHOL:
2730 	case PT_NWHL:
2731 		// whitehole eats anar
2732 		if (parts[i].type == PT_ANAR)
2733 		{
2734 			if (!legacy_enable)
2735 			{
2736 				parts[ID(r)].temp = restrict_flt(parts[ID(r)].temp - (MAX_TEMP-parts[i].temp)/2, MIN_TEMP, MAX_TEMP);
2737 			}
2738 			kill_part(i);
2739 			return 0;
2740 		}
2741 		break;
2742 	case PT_DEUT:
2743 		if (parts[i].type == PT_ELEC)
2744 		{
2745 			if(parts[ID(r)].life < 6000)
2746 				parts[ID(r)].life += 1;
2747 			parts[ID(r)].temp = 0;
2748 			kill_part(i);
2749 			return 0;
2750 		}
2751 		break;
2752 	case PT_VIBR:
2753 	case PT_BVBR:
2754 		if ((elements[parts[i].type].Properties & TYPE_ENERGY))
2755 		{
2756 			parts[ID(r)].tmp += 20;
2757 			kill_part(i);
2758 			return 0;
2759 		}
2760 		break;
2761 	}
2762 
2763 	switch (parts[i].type)
2764 	{
2765 	case PT_NEUT:
2766 		if (elements[TYP(r)].Properties & PROP_NEUTABSORB)
2767 		{
2768 			kill_part(i);
2769 			return 0;
2770 		}
2771 		break;
2772 	case PT_CNCT:
2773 		if (y < ny && TYP(pmap[y+1][x]) == PT_CNCT) //check below CNCT for another CNCT
2774 			return 0;
2775 		break;
2776 	case PT_GBMB:
2777 		if (parts[i].life > 0)
2778 			return 0;
2779 		break;
2780 	}
2781 
2782 	if ((bmap[y/CELL][x/CELL]==WL_EHOLE && !emap[y/CELL][x/CELL]) && !(bmap[ny/CELL][nx/CELL]==WL_EHOLE && !emap[ny/CELL][nx/CELL]))
2783 		return 0;
2784 
2785 	int ri = ID(r); //ri is the particle number at r (pmap[ny][nx])
2786 	if (r)//the swap part, if we make it this far, swap
2787 	{
2788 		if (parts[i].type==PT_NEUT) {
2789 			// target material is NEUTPENETRATE, meaning it gets moved around when neutron passes
2790 			unsigned s = pmap[y][x];
2791 			if (s && !(elements[TYP(s)].Properties&PROP_NEUTPENETRATE))
2792 				return 1; // if the element currently underneath neutron isn't NEUTPENETRATE, don't move anything except the neutron
2793 			// if nothing is currently underneath neutron, only move target particle
2794 			if(bmap[y/CELL][x/CELL] == WL_ALLOWENERGY)
2795 				return 1; // do not drag target particle into an energy only wall
2796 			if (s)
2797 			{
2798 				pmap[ny][nx] = (s&~PMAPMASK)|parts[ID(s)].type;
2799 				parts[ID(s)].x = nx;
2800 				parts[ID(s)].y = ny;
2801 			}
2802 			else
2803 				pmap[ny][nx] = 0;
2804 			parts[ri].x = x;
2805 			parts[ri].y = y;
2806 			pmap[y][x] = PMAP(ri, parts[ri].type);
2807 			return 1;
2808 		}
2809 
2810 		if (ID(pmap[ny][nx]) == ri)
2811 			pmap[ny][nx] = 0;
2812 		parts[ri].x += x-nx;
2813 		parts[ri].y += y-ny;
2814 		pmap[(int)(parts[ri].y+0.5f)][(int)(parts[ri].x+0.5f)] = PMAP(ri, parts[ri].type);
2815 	}
2816 	return 1;
2817 }
2818 
2819 // try to move particle, and if successful update pmap and parts[i].x,y
do_move(int i,int x,int y,float nxf,float nyf)2820 int Simulation::do_move(int i, int x, int y, float nxf, float nyf)
2821 {
2822 	int nx = (int)(nxf+0.5f), ny = (int)(nyf+0.5f), result;
2823 	if (edgeMode == 2)
2824 	{
2825 		bool x_ok = (nx >= CELL && nx < XRES-CELL);
2826 		bool y_ok = (ny >= CELL && ny < YRES-CELL);
2827 		if (!x_ok)
2828 			nxf = remainder_p(nxf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f;
2829 		if (!y_ok)
2830 			nyf = remainder_p(nyf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f;
2831 		nx = (int)(nxf+0.5f);
2832 		ny = (int)(nyf+0.5f);
2833 
2834 		/*if (!x_ok || !y_ok)
2835 		{
2836 			//make sure there isn't something blocking it on the other side
2837 			//only needed if this if statement is moved after the try_move (like my mod)
2838 			//if (!eval_move(t, nx, ny, NULL) || (t == PT_PHOT && pmap[ny][nx]))
2839 			//	return -1;
2840 		}*/
2841 	}
2842 	if (parts[i].type == PT_NONE)
2843 		return 0;
2844 	result = try_move(i, x, y, nx, ny);
2845 	if (result)
2846 	{
2847 		int t = parts[i].type;
2848 		parts[i].x = nxf;
2849 		parts[i].y = nyf;
2850 		if (ny!=y || nx!=x)
2851 		{
2852 			if (ID(pmap[y][x]) == i)
2853 				pmap[y][x] = 0;
2854 			if (ID(photons[y][x]) == i)
2855 				photons[y][x] = 0;
2856 			// kill_part if particle is out of bounds
2857 			if (nx < CELL || nx >= XRES - CELL || ny < CELL || ny >= YRES - CELL)
2858 			{
2859 				kill_part(i);
2860 				return -1;
2861 			}
2862 			if (elements[t].Properties & TYPE_ENERGY)
2863 				photons[ny][nx] = PMAP(i, t);
2864 			else if (t)
2865 				pmap[ny][nx] = PMAP(i, t);
2866 		}
2867 	}
2868 	return result;
2869 }
2870 
photoelectric_effect(int nx,int ny)2871 void Simulation::photoelectric_effect(int nx, int ny)//create sparks from PHOT when hitting PSCN and NSCN
2872 {
2873 	unsigned r = pmap[ny][nx];
2874 
2875 	if (TYP(r) == PT_PSCN)
2876 	{
2877 		if (TYP(pmap[ny][nx-1]) == PT_NSCN || TYP(pmap[ny][nx+1]) == PT_NSCN ||
2878 		        TYP(pmap[ny-1][nx]) == PT_NSCN ||  TYP(pmap[ny+1][nx]) == PT_NSCN)
2879 		{
2880 			parts[ID(r)].ctype = PT_PSCN;
2881 			part_change_type(ID(r), nx, ny, PT_SPRK);
2882 			parts[ID(r)].life = 4;
2883 		}
2884 	}
2885 }
2886 
direction_to_map(float dx,float dy,int t)2887 unsigned Simulation::direction_to_map(float dx, float dy, int t)
2888 {
2889 	// TODO:
2890 	// Adding extra directions causes some inaccuracies.
2891 	// Not adding them causes problems with some diagonal surfaces (photons absorbed instead of reflected).
2892 	// For now, don't add them.
2893 	// Solution may involve more intelligent setting of initial i0 value in find_next_boundary?
2894 	// or rewriting normal/boundary finding code
2895 
2896 	return (dx >= 0) |
2897 	       (((dx + dy) >= 0) << 1) |     /*  567  */
2898 	       ((dy >= 0) << 2) |            /*  4+0  */
2899 	       (((dy - dx) >= 0) << 3) |     /*  321  */
2900 	       ((dx <= 0) << 4) |
2901 	       (((dx + dy) <= 0) << 5) |
2902 	       ((dy <= 0) << 6) |
2903 	       (((dy - dx) <= 0) << 7);
2904 	/*
2905 	return (dx >= -0.001) |
2906 	       (((dx + dy) >= -0.001) << 1) |     //  567
2907 	       ((dy >= -0.001) << 2) |            //  4+0
2908 	       (((dy - dx) >= -0.001) << 3) |     //  321
2909 	       ((dx <= 0.001) << 4) |
2910 	       (((dx + dy) <= 0.001) << 5) |
2911 	       ((dy <= 0.001) << 6) |
2912 	       (((dy - dx) <= 0.001) << 7);
2913 	}*/
2914 }
2915 
is_blocking(int t,int x,int y)2916 int Simulation::is_blocking(int t, int x, int y)
2917 {
2918 	if (t & REFRACT) {
2919 		if (x<0 || y<0 || x>=XRES || y>=YRES)
2920 			return 0;
2921 		if (TYP(pmap[y][x]) == PT_GLAS || TYP(pmap[y][x]) == PT_BGLA)
2922 			return 1;
2923 		return 0;
2924 	}
2925 
2926 	return !eval_move(t, x, y, NULL);
2927 }
2928 
is_boundary(int pt,int x,int y)2929 int Simulation::is_boundary(int pt, int x, int y)
2930 {
2931 	if (!is_blocking(pt,x,y))
2932 		return 0;
2933 	if (is_blocking(pt,x,y-1) && is_blocking(pt,x,y+1) && is_blocking(pt,x-1,y) && is_blocking(pt,x+1,y))
2934 		return 0;
2935 	return 1;
2936 }
2937 
find_next_boundary(int pt,int * x,int * y,int dm,int * em)2938 int Simulation::find_next_boundary(int pt, int *x, int *y, int dm, int *em)
2939 {
2940 	static int dx[8] = {1,1,0,-1,-1,-1,0,1};
2941 	static int dy[8] = {0,1,1,1,0,-1,-1,-1};
2942 	static int de[8] = {0x83,0x07,0x0E,0x1C,0x38,0x70,0xE0,0xC1};
2943 	int i, ii, i0;
2944 
2945 	if (*x <= 0 || *x >= XRES-1 || *y <= 0 || *y >= YRES-1)
2946 		return 0;
2947 
2948 	if (*em != -1) {
2949 		i0 = *em;
2950 		dm &= de[i0];
2951 	} else
2952 		i0 = 0;
2953 
2954 	for (ii=0; ii<8; ii++) {
2955 		i = (ii + i0) & 7;
2956 		if ((dm & (1 << i)) && is_boundary(pt, *x+dx[i], *y+dy[i])) {
2957 			*x += dx[i];
2958 			*y += dy[i];
2959 			*em = i;
2960 			return 1;
2961 		}
2962 	}
2963 
2964 	return 0;
2965 }
2966 
get_normal(int pt,int x,int y,float dx,float dy,float * nx,float * ny)2967 int Simulation::get_normal(int pt, int x, int y, float dx, float dy, float *nx, float *ny)
2968 {
2969 	int ldm, rdm, lm, rm;
2970 	int lx, ly, lv, rx, ry, rv;
2971 	int i, j;
2972 	float r, ex, ey;
2973 
2974 	if (!dx && !dy)
2975 		return 0;
2976 
2977 	if (!is_boundary(pt, x, y))
2978 		return 0;
2979 
2980 	ldm = direction_to_map(-dy, dx, pt);
2981 	rdm = direction_to_map(dy, -dx, pt);
2982 	lx = rx = x;
2983 	ly = ry = y;
2984 	lv = rv = 1;
2985 	lm = rm = -1;
2986 
2987 	j = 0;
2988 	for (i=0; i<SURF_RANGE; i++) {
2989 		if (lv)
2990 			lv = find_next_boundary(pt, &lx, &ly, ldm, &lm);
2991 		if (rv)
2992 			rv = find_next_boundary(pt, &rx, &ry, rdm, &rm);
2993 		j += lv + rv;
2994 		if (!lv && !rv)
2995 			break;
2996 	}
2997 
2998 	if (j < NORMAL_MIN_EST)
2999 		return 0;
3000 
3001 	if ((lx == rx) && (ly == ry))
3002 		return 0;
3003 
3004 	ex = rx - lx;
3005 	ey = ry - ly;
3006 	r = 1.0f/hypot(ex, ey);
3007 	*nx =  ey * r;
3008 	*ny = -ex * r;
3009 
3010 	return 1;
3011 }
3012 
get_normal_interp(int pt,float x0,float y0,float dx,float dy,float * nx,float * ny)3013 int Simulation::get_normal_interp(int pt, float x0, float y0, float dx, float dy, float *nx, float *ny)
3014 {
3015 	int x, y, i;
3016 
3017 	dx /= NORMAL_FRAC;
3018 	dy /= NORMAL_FRAC;
3019 
3020 	for (i=0; i<NORMAL_INTERP; i++) {
3021 		x = (int)(x0 + 0.5f);
3022 		y = (int)(y0 + 0.5f);
3023 		if (is_boundary(pt, x, y))
3024 			break;
3025 		x0 += dx;
3026 		y0 += dy;
3027 	}
3028 	if (i >= NORMAL_INTERP)
3029 		return 0;
3030 
3031 	if (pt == PT_PHOT)
3032 		photoelectric_effect(x, y);
3033 
3034 	return get_normal(pt, x, y, dx, dy, nx, ny);
3035 }
3036 
kill_part(int i)3037 void Simulation::kill_part(int i)//kills particle number i
3038 {
3039 	int x = (int)(parts[i].x + 0.5f);
3040 	int y = (int)(parts[i].y + 0.5f);
3041 
3042 	int t = parts[i].type;
3043 	if (t && elements[t].ChangeType)
3044 	{
3045 		(*(elements[t].ChangeType))(this, i, x, y, t, PT_NONE);
3046 	}
3047 
3048 	if (x >= 0 && y >= 0 && x < XRES && y < YRES)
3049 	{
3050 		if (ID(pmap[y][x]) == i)
3051 			pmap[y][x] = 0;
3052 		else if (ID(photons[y][x]) == i)
3053 			photons[y][x] = 0;
3054 	}
3055 
3056 	// This shouldn't happen but ... you never know?
3057 	if (t == PT_NONE)
3058 		return;
3059 
3060 	elementCount[t]--;
3061 
3062 	parts[i].type = PT_NONE;
3063 	parts[i].life = pfree;
3064 	pfree = i;
3065 }
3066 
3067 // Changes the type of particle number i, to t.  This also changes pmap at the same time
3068 // Returns true if the particle was killed
part_change_type(int i,int x,int y,int t)3069 bool Simulation::part_change_type(int i, int x, int y, int t)
3070 {
3071 	if (x<0 || y<0 || x>=XRES || y>=YRES || i>=NPART || t<0 || t>=PT_NUM || !parts[i].type)
3072 		return false;
3073 	if (!elements[t].Enabled || t == PT_NONE)
3074 	{
3075 		kill_part(i);
3076 		return true;
3077 	}
3078 	if (elements[t].CreateAllowed)
3079 	{
3080 		if (!(*(elements[t].CreateAllowed))(this, i, x, y, t))
3081 			return false;
3082 	}
3083 
3084 	if (elements[parts[i].type].ChangeType)
3085 		(*(elements[parts[i].type].ChangeType))(this, i, x, y, parts[i].type, t);
3086 	if (elements[t].ChangeType)
3087 		(*(elements[t].ChangeType))(this, i, x, y, parts[i].type, t);
3088 
3089 	if (parts[i].type > 0 && parts[i].type < PT_NUM && elementCount[parts[i].type])
3090 		elementCount[parts[i].type]--;
3091 	elementCount[t]++;
3092 
3093 	parts[i].type = t;
3094 	if (elements[t].Properties & TYPE_ENERGY)
3095 	{
3096 		photons[y][x] = PMAP(i, t);
3097 		if (ID(pmap[y][x]) == i)
3098 			pmap[y][x] = 0;
3099 	}
3100 	else
3101 	{
3102 		pmap[y][x] = PMAP(i, t);
3103 		if (ID(photons[y][x]) == i)
3104 			photons[y][x] = 0;
3105 	}
3106 	return false;
3107 }
3108 
3109 //the function for creating a particle, use p=-1 for creating a new particle, -2 is from a brush, or a particle number to replace a particle.
3110 //tv = Type (PMAPBITS bits) + Var (32-PMAPBITS bits), var is usually 0
create_part(int p,int x,int y,int t,int v)3111 int Simulation::create_part(int p, int x, int y, int t, int v)
3112 {
3113 	int i, oldType = PT_NONE;
3114 
3115 	if (x<0 || y<0 || x>=XRES || y>=YRES)
3116 		return -1;
3117 	if (t>=0 && t<PT_NUM && !elements[t].Enabled)
3118 		return -1;
3119 
3120 	if (t == PT_SPRK && !(p == -2 && elements[TYP(pmap[y][x])].CtypeDraw))
3121 	{
3122 		int type = TYP(pmap[y][x]);
3123 		int index = ID(pmap[y][x]);
3124 		if(type == PT_WIRE)
3125 		{
3126 			parts[index].ctype = PT_DUST;
3127 			return index;
3128 		}
3129 		if (!(type == PT_INST || (elements[type].Properties&PROP_CONDUCTS)) || parts[index].life!=0)
3130 			return -1;
3131 		if (p == -2 && type == PT_INST)
3132 		{
3133 			FloodINST(x, y);
3134 			return index;
3135 		}
3136 		parts[index].type = PT_SPRK;
3137 		parts[index].life = 4;
3138 		parts[index].ctype = type;
3139 		pmap[y][x] = (pmap[y][x]&~PMAPMASK) | PT_SPRK;
3140 		if (parts[index].temp+10.0f < 673.0f && !legacy_enable && (type==PT_METL || type == PT_BMTL || type == PT_BRMT || type == PT_PSCN || type == PT_NSCN || type == PT_ETRD || type == PT_NBLE || type == PT_IRON))
3141 			parts[index].temp = parts[index].temp+10.0f;
3142 		return index;
3143 	}
3144 
3145 	if (p == -2)
3146 	{
3147 		if (pmap[y][x])
3148 		{
3149 			int drawOn = TYP(pmap[y][x]);
3150 			if (elements[drawOn].CtypeDraw)
3151 				elements[drawOn].CtypeDraw(this, ID(pmap[y][x]), t, v);
3152 			return -1;
3153 		}
3154 		else if (IsWallBlocking(x, y, t))
3155 			return -1;
3156 		else if (photons[y][x] && (elements[t].Properties & TYPE_ENERGY))
3157 			return -1;
3158 	}
3159 
3160 	if (elements[t].CreateAllowed)
3161 	{
3162 		if (!(*(elements[t].CreateAllowed))(this, p, x, y, t))
3163 			return -1;
3164 	}
3165 
3166 	if (p == -1)//creating from anything but brush
3167 	{
3168 		// If there is a particle, only allow creation if the new particle can occupy the same space as the existing particle
3169 		// If there isn't a particle but there is a wall, check whether the new particle is allowed to be in it
3170 		//   (not "!=2" for wall check because eval_move returns 1 for moving into empty space)
3171 		// If there's no particle and no wall, assume creation is allowed
3172 		if (pmap[y][x] ? (eval_move(t, x, y, NULL) != 2) : (bmap[y/CELL][x/CELL] && eval_move(t, x, y, NULL) == 0))
3173 		{
3174 			return -1;
3175 		}
3176 		if (pfree == -1)
3177 			return -1;
3178 		i = pfree;
3179 		pfree = parts[i].life;
3180 	}
3181 	else if (p == -2)//creating from brush
3182 	{
3183 		if (pfree == -1)
3184 			return -1;
3185 		i = pfree;
3186 		pfree = parts[i].life;
3187 	}
3188 	else if (p == -3)//skip pmap checks, e.g. for sing explosion
3189 	{
3190 		if (pfree == -1)
3191 			return -1;
3192 		i = pfree;
3193 		pfree = parts[i].life;
3194 	}
3195 	else
3196 	{
3197 		int oldX = (int)(parts[p].x + 0.5f);
3198 		int oldY = (int)(parts[p].y + 0.5f);
3199 		if (ID(pmap[oldY][oldX]) == p)
3200 			pmap[oldY][oldX] = 0;
3201 		if (ID(photons[oldY][oldX]) == p)
3202 			photons[oldY][oldX] = 0;
3203 
3204 		oldType = parts[p].type;
3205 
3206 		if (elements[oldType].ChangeType)
3207 			(*(elements[oldType].ChangeType))(this, p, oldX, oldY, oldType, t);
3208 		if (oldType)
3209 			elementCount[oldType]--;
3210 
3211 		i = p;
3212 	}
3213 
3214 	if (i>parts_lastActiveIndex) parts_lastActiveIndex = i;
3215 
3216 	parts[i] = elements[t].DefaultProperties;
3217 	parts[i].type = t;
3218 	parts[i].x = (float)x;
3219 	parts[i].y = (float)y;
3220 
3221 	//and finally set the pmap/photon maps to the newly created particle
3222 	if (elements[t].Properties & TYPE_ENERGY)
3223 		photons[y][x] = PMAP(i, t);
3224 	else if (t!=PT_STKM && t!=PT_STKM2 && t!=PT_FIGH)
3225 		pmap[y][x] = PMAP(i, t);
3226 
3227 	//Fancy dust effects for powder types
3228 	if((elements[t].Properties & TYPE_PART) && pretty_powder)
3229 	{
3230 		int colr, colg, colb;
3231 		colr = PIXR(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15);
3232 		colg = PIXG(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15);
3233 		colb = PIXB(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15);
3234 		colr = colr>255 ? 255 : (colr<0 ? 0 : colr);
3235 		colg = colg>255 ? 255 : (colg<0 ? 0 : colg);
3236 		colb = colb>255 ? 255 : (colb<0 ? 0 : colb);
3237 		parts[i].dcolour = (RNG::Ref().between(0, 149)<<24) | (colr<<16) | (colg<<8) | colb;
3238 	}
3239 
3240 	// Set non-static properties (such as randomly generated ones)
3241 	if (elements[t].Create)
3242 		(*(elements[t].Create))(this, i, x, y, t, v);
3243 
3244 	if (elements[t].ChangeType)
3245 		(*(elements[t].ChangeType))(this, i, x, y, oldType, t);
3246 
3247 	elementCount[t]++;
3248 	return i;
3249 }
3250 
GetGravityField(int x,int y,float particleGrav,float newtonGrav,float & pGravX,float & pGravY)3251 void Simulation::GetGravityField(int x, int y, float particleGrav, float newtonGrav, float & pGravX, float & pGravY)
3252 {
3253 	pGravX = newtonGrav*gravx[(y/CELL)*(XRES/CELL)+(x/CELL)];
3254 	pGravY = newtonGrav*gravy[(y/CELL)*(XRES/CELL)+(x/CELL)];
3255 	switch (gravityMode)
3256 	{
3257 		default:
3258 		case 0: //normal, vertical gravity
3259 			pGravY += particleGrav;
3260 			break;
3261 		case 1: //no gravity
3262 			break;
3263 		case 2: //radial gravity
3264 			if (x-XCNTR != 0 || y-YCNTR != 0)
3265 			{
3266 				float pGravMult = particleGrav/sqrtf((x-XCNTR)*(x-XCNTR) + (y-YCNTR)*(y-YCNTR));
3267 				pGravX -= pGravMult * (float)(x - XCNTR);
3268 				pGravY -= pGravMult * (float)(y - YCNTR);
3269 			}
3270 	}
3271 }
3272 
create_gain_photon(int pp)3273 void Simulation::create_gain_photon(int pp)//photons from PHOT going through GLOW
3274 {
3275 	float xx, yy;
3276 	int i, lr, temp_bin, nx, ny;
3277 
3278 	if (pfree == -1)
3279 		return;
3280 	i = pfree;
3281 
3282 	lr = RNG::Ref().between(0, 1);
3283 
3284 	if (lr) {
3285 		xx = parts[pp].x - 0.3*parts[pp].vy;
3286 		yy = parts[pp].y + 0.3*parts[pp].vx;
3287 	} else {
3288 		xx = parts[pp].x + 0.3*parts[pp].vy;
3289 		yy = parts[pp].y - 0.3*parts[pp].vx;
3290 	}
3291 
3292 	nx = (int)(xx + 0.5f);
3293 	ny = (int)(yy + 0.5f);
3294 
3295 	if (nx<0 || ny<0 || nx>=XRES || ny>=YRES)
3296 		return;
3297 
3298 	if (TYP(pmap[ny][nx]) != PT_GLOW)
3299 		return;
3300 
3301 	pfree = parts[i].life;
3302 	if (i>parts_lastActiveIndex) parts_lastActiveIndex = i;
3303 
3304 	parts[i].type = PT_PHOT;
3305 	parts[i].life = 680;
3306 	parts[i].x = xx;
3307 	parts[i].y = yy;
3308 	parts[i].vx = parts[pp].vx;
3309 	parts[i].vy = parts[pp].vy;
3310 	parts[i].temp = parts[ID(pmap[ny][nx])].temp;
3311 	parts[i].tmp = 0;
3312 	parts[i].pavg[0] = parts[i].pavg[1] = 0.0f;
3313 	photons[ny][nx] = PMAP(i, PT_PHOT);
3314 
3315 	temp_bin = (int)((parts[i].temp-273.0f)*0.25f);
3316 	if (temp_bin < 0) temp_bin = 0;
3317 	if (temp_bin > 25) temp_bin = 25;
3318 	parts[i].ctype = 0x1F << temp_bin;
3319 }
3320 
create_cherenkov_photon(int pp)3321 void Simulation::create_cherenkov_photon(int pp)//photons from NEUT going through GLAS
3322 {
3323 	int i, lr, nx, ny;
3324 	float r;
3325 
3326 	if (pfree == -1)
3327 		return;
3328 	i = pfree;
3329 
3330 	nx = (int)(parts[pp].x + 0.5f);
3331 	ny = (int)(parts[pp].y + 0.5f);
3332 	if (TYP(pmap[ny][nx]) != PT_GLAS && TYP(pmap[ny][nx]) != PT_BGLA)
3333 		return;
3334 
3335 	if (hypotf(parts[pp].vx, parts[pp].vy) < 1.44f)
3336 		return;
3337 
3338 	pfree = parts[i].life;
3339 	if (i>parts_lastActiveIndex) parts_lastActiveIndex = i;
3340 
3341 	lr = RNG::Ref().between(0, 1);
3342 
3343 	parts[i].type = PT_PHOT;
3344 	parts[i].ctype = 0x00000F80;
3345 	parts[i].life = 680;
3346 	parts[i].x = parts[pp].x;
3347 	parts[i].y = parts[pp].y;
3348 	parts[i].temp = parts[ID(pmap[ny][nx])].temp;
3349 	parts[i].tmp = 0;
3350 	parts[i].pavg[0] = parts[i].pavg[1] = 0.0f;
3351 	photons[ny][nx] = PMAP(i, PT_PHOT);
3352 
3353 	if (lr) {
3354 		parts[i].vx = parts[pp].vx - 2.5f*parts[pp].vy;
3355 		parts[i].vy = parts[pp].vy + 2.5f*parts[pp].vx;
3356 	} else {
3357 		parts[i].vx = parts[pp].vx + 2.5f*parts[pp].vy;
3358 		parts[i].vy = parts[pp].vy - 2.5f*parts[pp].vx;
3359 	}
3360 
3361 	/* photons have speed of light. no discussion. */
3362 	r = 1.269 / hypotf(parts[i].vx, parts[i].vy);
3363 	parts[i].vx *= r;
3364 	parts[i].vy *= r;
3365 }
3366 
delete_part(int x,int y)3367 void Simulation::delete_part(int x, int y)//calls kill_part with the particle located at x,y
3368 {
3369 	unsigned i;
3370 
3371 	if (x<0 || y<0 || x>=XRES || y>=YRES)
3372 		return;
3373 	if (photons[y][x]) {
3374 		i = photons[y][x];
3375 	} else {
3376 		i = pmap[y][x];
3377 	}
3378 
3379 	if (!i)
3380 		return;
3381 	kill_part(ID(i));
3382 }
3383 
UpdateParticles(int start,int end)3384 void Simulation::UpdateParticles(int start, int end)
3385 {
3386 	int i, j, x, y, t, nx, ny, r, surround_space, s, rt, nt;
3387 	float mv, dx, dy, nrx, nry, dp, ctemph, ctempl, gravtot;
3388 	int fin_x, fin_y, clear_x, clear_y, stagnant;
3389 	float fin_xf, fin_yf, clear_xf, clear_yf;
3390 	float nn, ct1, ct2, swappage;
3391 	float pt = R_TEMP;
3392 	float c_heat = 0.0f;
3393 	int h_count = 0;
3394 	int surround[8];
3395 	int surround_hconduct[8];
3396 	float pGravX, pGravY, pGravD;
3397 	bool transitionOccurred;
3398 
3399 	//the main particle loop function, goes over all particles.
3400 	for (i = start; i <= end && i <= parts_lastActiveIndex; i++)
3401 		if (parts[i].type)
3402 		{
3403 			t = parts[i].type;
3404 
3405 			x = (int)(parts[i].x+0.5f);
3406 			y = (int)(parts[i].y+0.5f);
3407 
3408 			//this kills any particle out of the screen, or in a wall where it isn't supposed to go
3409 			if (x<CELL || y<CELL || x>=XRES-CELL || y>=YRES-CELL ||
3410 			        (bmap[y/CELL][x/CELL] &&
3411 			         (bmap[y/CELL][x/CELL]==WL_WALL ||
3412 			          bmap[y/CELL][x/CELL]==WL_WALLELEC ||
3413 			          bmap[y/CELL][x/CELL]==WL_ALLOWAIR ||
3414 			          (bmap[y/CELL][x/CELL]==WL_DESTROYALL) ||
3415 			          (bmap[y/CELL][x/CELL]==WL_ALLOWLIQUID && !(elements[t].Properties&TYPE_LIQUID)) ||
3416 			          (bmap[y/CELL][x/CELL]==WL_ALLOWPOWDER && !(elements[t].Properties&TYPE_PART)) ||
3417 			          (bmap[y/CELL][x/CELL]==WL_ALLOWGAS && !(elements[t].Properties&TYPE_GAS)) || //&& elements[t].Falldown!=0 && parts[i].type!=PT_FIRE && parts[i].type!=PT_SMKE && parts[i].type!=PT_CFLM) ||
3418 			          (bmap[y/CELL][x/CELL]==WL_ALLOWENERGY && !(elements[t].Properties&TYPE_ENERGY)) ||
3419 					  (bmap[y/CELL][x/CELL]==WL_DETECT && (t==PT_METL || t==PT_SPRK)) ||
3420 			          (bmap[y/CELL][x/CELL]==WL_EWALL && !emap[y/CELL][x/CELL])) && (t!=PT_STKM) && (t!=PT_STKM2) && (t!=PT_FIGH)))
3421 			{
3422 				kill_part(i);
3423 				continue;
3424 			}
3425 
3426 			// Make sure that STASIS'd particles don't tick.
3427 			if (bmap[y/CELL][x/CELL] == WL_STASIS && emap[y/CELL][x/CELL]<8) {
3428 				continue;
3429 			}
3430 
3431 			if (bmap[y/CELL][x/CELL]==WL_DETECT && emap[y/CELL][x/CELL]<8)
3432 				set_emap(x/CELL, y/CELL);
3433 
3434 			//adding to velocity from the particle's velocity
3435 			vx[y/CELL][x/CELL] = vx[y/CELL][x/CELL]*elements[t].AirLoss + elements[t].AirDrag*parts[i].vx;
3436 			vy[y/CELL][x/CELL] = vy[y/CELL][x/CELL]*elements[t].AirLoss + elements[t].AirDrag*parts[i].vy;
3437 
3438 			if (elements[t].HotAir)
3439 			{
3440 				if (t==PT_GAS||t==PT_NBLE)
3441 				{
3442 					if (pv[y/CELL][x/CELL]<3.5f)
3443 						pv[y/CELL][x/CELL] += elements[t].HotAir*(3.5f-pv[y/CELL][x/CELL]);
3444 					if (y+CELL<YRES && pv[y/CELL+1][x/CELL]<3.5f)
3445 						pv[y/CELL+1][x/CELL] += elements[t].HotAir*(3.5f-pv[y/CELL+1][x/CELL]);
3446 					if (x+CELL<XRES)
3447 					{
3448 						if (pv[y/CELL][x/CELL+1]<3.5f)
3449 							pv[y/CELL][x/CELL+1] += elements[t].HotAir*(3.5f-pv[y/CELL][x/CELL+1]);
3450 						if (y+CELL<YRES && pv[y/CELL+1][x/CELL+1]<3.5f)
3451 							pv[y/CELL+1][x/CELL+1] += elements[t].HotAir*(3.5f-pv[y/CELL+1][x/CELL+1]);
3452 					}
3453 				}
3454 				else//add the hotair variable to the pressure map, like black hole, or white hole.
3455 				{
3456 					pv[y/CELL][x/CELL] += elements[t].HotAir;
3457 					if (y+CELL<YRES)
3458 						pv[y/CELL+1][x/CELL] += elements[t].HotAir;
3459 					if (x+CELL<XRES)
3460 					{
3461 						pv[y/CELL][x/CELL+1] += elements[t].HotAir;
3462 						if (y+CELL<YRES)
3463 							pv[y/CELL+1][x/CELL+1] += elements[t].HotAir;
3464 					}
3465 				}
3466 			}
3467 
3468 			pGravX = pGravY = 0;
3469 			if (!(elements[t].Properties & TYPE_SOLID))
3470 			{
3471 				if (elements[t].Gravity)
3472 				{
3473 					//Gravity mode by Moach
3474 					switch (gravityMode)
3475 					{
3476 					default:
3477 					case 0:
3478 						pGravX = 0.0f;
3479 						pGravY = elements[t].Gravity;
3480 						break;
3481 					case 1:
3482 						pGravX = pGravY = 0.0f;
3483 						break;
3484 					case 2:
3485 						pGravD = 0.01f - hypotf((x - XCNTR), (y - YCNTR));
3486 						pGravX = elements[t].Gravity * ((float)(x - XCNTR) / pGravD);
3487 						pGravY = elements[t].Gravity * ((float)(y - YCNTR) / pGravD);
3488 						break;
3489 					}
3490 				}
3491 				if (elements[t].NewtonianGravity)
3492 				{
3493 					//Get some gravity from the gravity map
3494 					pGravX += elements[t].NewtonianGravity * gravx[(y/CELL)*(XRES/CELL)+(x/CELL)];
3495 					pGravY += elements[t].NewtonianGravity * gravy[(y/CELL)*(XRES/CELL)+(x/CELL)];
3496 				}
3497 			}
3498 
3499 			//velocity updates for the particle
3500 			if (t != PT_SPNG || !(parts[i].flags&FLAG_MOVABLE))
3501 			{
3502 				parts[i].vx *= elements[t].Loss;
3503 				parts[i].vy *= elements[t].Loss;
3504 			}
3505 			//particle gets velocity from the vx and vy maps
3506 			parts[i].vx += elements[t].Advection*vx[y/CELL][x/CELL] + pGravX;
3507 			parts[i].vy += elements[t].Advection*vy[y/CELL][x/CELL] + pGravY;
3508 
3509 
3510 			if (elements[t].Diffusion)//the random diffusion that gasses have
3511 			{
3512 #ifdef REALISTIC
3513 				//The magic number controls diffusion speed
3514 				parts[i].vx += 0.05*sqrtf(parts[i].temp)*elements[t].Diffusion*(2.0f*RNG::Ref().uniform01()-1.0f);
3515 				parts[i].vy += 0.05*sqrtf(parts[i].temp)*elements[t].Diffusion*(2.0f*RNG::Ref().uniform01()-1.0f);
3516 #else
3517 				parts[i].vx += elements[t].Diffusion*(2.0f*RNG::Ref().uniform01()-1.0f);
3518 				parts[i].vy += elements[t].Diffusion*(2.0f*RNG::Ref().uniform01()-1.0f);
3519 #endif
3520 			}
3521 
3522 			transitionOccurred = false;
3523 
3524 			j = surround_space = nt = 0;//if nt is greater than 1 after this, then there is a particle around the current particle, that is NOT the current particle's type, for water movement.
3525 			for (nx=-1; nx<2; nx++)
3526 				for (ny=-1; ny<2; ny++) {
3527 					if (nx||ny) {
3528 						surround[j] = r = pmap[y+ny][x+nx];
3529 						j++;
3530 						if (!TYP(r))
3531 							surround_space++;//there is empty space
3532 						if (TYP(r)!=t)
3533 							nt++;//there is nothing or a different particle
3534 					}
3535 				}
3536 
3537 			float gel_scale = 1.0f;
3538 			if (t==PT_GEL)
3539 				gel_scale = parts[i].tmp*2.55f;
3540 
3541 			if (!legacy_enable)
3542 			{
3543 				if (y-2 >= 0 && y-2 < YRES && (elements[t].Properties&TYPE_LIQUID) && (t!=PT_GEL || gel_scale > (1 + RNG::Ref().between(0, 254)))) {//some heat convection for liquids
3544 					r = pmap[y-2][x];
3545 					if (!(!r || parts[i].type != TYP(r))) {
3546 						if (parts[i].temp>parts[ID(r)].temp) {
3547 							swappage = parts[i].temp;
3548 							parts[i].temp = parts[ID(r)].temp;
3549 							parts[ID(r)].temp = swappage;
3550 						}
3551 					}
3552 				}
3553 
3554 				//heat transfer code
3555 				h_count = 0;
3556 #ifdef REALISTIC
3557 				if (t&&(t!=PT_HSWC||parts[i].life==10)&&(elements[t].HeatConduct*gel_scale))
3558 #else
3559 				if (t && (t!=PT_HSWC||parts[i].life==10) && RNG::Ref().chance(elements[t].HeatConduct*gel_scale, 250))
3560 #endif
3561 				{
3562 					if (aheat_enable && !(elements[t].Properties&PROP_NOAMBHEAT))
3563 					{
3564 #ifdef REALISTIC
3565 						c_heat = parts[i].temp*96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight) + hv[y/CELL][x/CELL]*100*(pv[y/CELL][x/CELL]+273.15f)/256;
3566 						float c_Cm = 96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight)  + 100*(pv[y/CELL][x/CELL]+273.15f)/256;
3567 						pt = c_heat/c_Cm;
3568 						pt = restrict_flt(pt, -MAX_TEMP+MIN_TEMP, MAX_TEMP-MIN_TEMP);
3569 						parts[i].temp = pt;
3570 						//Pressure increase from heat (temporary)
3571 						pv[y/CELL][x/CELL] += (pt-hv[y/CELL][x/CELL])*0.004;
3572 						hv[y/CELL][x/CELL] = pt;
3573 #else
3574 						c_heat = (hv[y/CELL][x/CELL]-parts[i].temp)*0.04;
3575 						c_heat = restrict_flt(c_heat, -MAX_TEMP+MIN_TEMP, MAX_TEMP-MIN_TEMP);
3576 						parts[i].temp += c_heat;
3577 						hv[y/CELL][x/CELL] -= c_heat;
3578 #endif
3579 					}
3580 					c_heat = 0.0f;
3581 #ifdef REALISTIC
3582 					float c_Cm = 0.0f;
3583 #endif
3584 					for (j=0; j<8; j++)
3585 					{
3586 						surround_hconduct[j] = i;
3587 						r = surround[j];
3588 						if (!r)
3589 							continue;
3590 						rt = TYP(r);
3591 						if (rt && elements[rt].HeatConduct && (rt!=PT_HSWC||parts[ID(r)].life==10)
3592 						        && (t!=PT_FILT||(rt!=PT_BRAY&&rt!=PT_BIZR&&rt!=PT_BIZRG))
3593 						        && (rt!=PT_FILT||(t!=PT_BRAY&&t!=PT_PHOT&&t!=PT_BIZR&&t!=PT_BIZRG))
3594 						        && (t!=PT_ELEC||rt!=PT_DEUT)
3595 						        && (t!=PT_DEUT||rt!=PT_ELEC)
3596 						        && (t!=PT_HSWC || rt!=PT_FILT || parts[i].tmp != 1)
3597 						        && (t!=PT_FILT || rt!=PT_HSWC || parts[ID(r)].tmp != 1))
3598 						{
3599 							surround_hconduct[j] = ID(r);
3600 #ifdef REALISTIC
3601 							if (rt==PT_GEL)
3602 								gel_scale = parts[ID(r)].tmp*2.55f;
3603 							else gel_scale = 1.0f;
3604 
3605 							c_heat += parts[ID(r)].temp*96.645/elements[rt].HeatConduct*gel_scale*fabs(elements[rt].Weight);
3606 							c_Cm += 96.645/elements[rt].HeatConduct*gel_scale*fabs(elements[rt].Weight);
3607 #else
3608 							c_heat += parts[ID(r)].temp;
3609 #endif
3610 							h_count++;
3611 						}
3612 					}
3613 #ifdef REALISTIC
3614 					if (t==PT_GEL)
3615 						gel_scale = parts[i].tmp*2.55f;
3616 					else gel_scale = 1.0f;
3617 
3618 					if (t == PT_PHOT)
3619 						pt = (c_heat+parts[i].temp*96.645)/(c_Cm+96.645);
3620 					else
3621 						pt = (c_heat+parts[i].temp*96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight))/(c_Cm+96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight));
3622 
3623 					c_heat += parts[i].temp*96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight);
3624 					c_Cm += 96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight);
3625 					parts[i].temp = restrict_flt(pt, MIN_TEMP, MAX_TEMP);
3626 #else
3627 					pt = (c_heat+parts[i].temp)/(h_count+1);
3628 					pt = parts[i].temp = restrict_flt(pt, MIN_TEMP, MAX_TEMP);
3629 					for (j=0; j<8; j++)
3630 					{
3631 						parts[surround_hconduct[j]].temp = pt;
3632 					}
3633 #endif
3634 
3635 					ctemph = ctempl = pt;
3636 					// change boiling point with pressure
3637 					if (((elements[t].Properties&TYPE_LIQUID) && IsValidElement(elements[t].HighTemperatureTransition) && (elements[elements[t].HighTemperatureTransition].Properties&TYPE_GAS))
3638 					        || t==PT_LNTG || t==PT_SLTW)
3639 						ctemph -= 2.0f*pv[y/CELL][x/CELL];
3640 					else if (((elements[t].Properties&TYPE_GAS) && IsValidElement(elements[t].LowTemperatureTransition) && (elements[elements[t].LowTemperatureTransition].Properties&TYPE_LIQUID))
3641 					         || t==PT_WTRV)
3642 						ctempl -= 2.0f*pv[y/CELL][x/CELL];
3643 					s = 1;
3644 
3645 					//A fix for ice with ctype = 0
3646 					if ((t==PT_ICEI || t==PT_SNOW) && (!parts[i].ctype || !IsValidElement(parts[i].ctype) || parts[i].ctype==PT_ICEI || parts[i].ctype==PT_SNOW))
3647 						parts[i].ctype = PT_WATR;
3648 
3649 					if (elements[t].HighTemperatureTransition>-1 && ctemph>=elements[t].HighTemperature)
3650 					{
3651 						// particle type change due to high temperature
3652 #ifdef REALISTIC
3653 						float dbt = ctempl - pt;
3654 						if (elements[t].HighTemperatureTransition != PT_NUM)
3655 						{
3656 							if (platent[t] <= (c_heat - (elements[t].HighTemperature - dbt)*c_Cm))
3657 							{
3658 								pt = (c_heat - platent[t])/c_Cm;
3659 								t = elements[t].HighTemperatureTransition;
3660 							}
3661 							else
3662 							{
3663 								parts[i].temp = restrict_flt(elements[t].HighTemperature - dbt, MIN_TEMP, MAX_TEMP);
3664 								s = 0;
3665 							}
3666 						}
3667 #else
3668 						if (elements[t].HighTemperatureTransition != PT_NUM)
3669 							t = elements[t].HighTemperatureTransition;
3670 #endif
3671 						else if (t == PT_ICEI || t == PT_SNOW)
3672 						{
3673 							if (parts[i].ctype > 0 && parts[i].ctype < PT_NUM && parts[i].ctype != t)
3674 							{
3675 								if (elements[parts[i].ctype].LowTemperatureTransition==PT_ICEI || elements[parts[i].ctype].LowTemperatureTransition==PT_SNOW)
3676 								{
3677 									if (pt<elements[parts[i].ctype].LowTemperature)
3678 										s = 0;
3679 								}
3680 								else if (pt<273.15f)
3681 									s = 0;
3682 
3683 								if (s)
3684 								{
3685 #ifdef REALISTIC
3686 									//One ice table value for all it's kinds
3687 									if (platent[t] <= (c_heat - (elements[parts[i].ctype].LowTemperature - dbt)*c_Cm))
3688 									{
3689 										pt = (c_heat - platent[t])/c_Cm;
3690 										t = parts[i].ctype;
3691 										parts[i].ctype = PT_NONE;
3692 										parts[i].life = 0;
3693 									}
3694 									else
3695 									{
3696 										parts[i].temp = restrict_flt(elements[parts[i].ctype].LowTemperature - dbt, MIN_TEMP, MAX_TEMP);
3697 										s = 0;
3698 									}
3699 #else
3700 									t = parts[i].ctype;
3701 									parts[i].ctype = PT_NONE;
3702 									parts[i].life = 0;
3703 #endif
3704 								}
3705 							}
3706 							else
3707 								s = 0;
3708 						}
3709 						else if (t == PT_SLTW)
3710 						{
3711 #ifdef REALISTIC
3712 							if (platent[t] <= (c_heat - (elements[t].HighTemperature - dbt)*c_Cm))
3713 							{
3714 								pt = (c_heat - platent[t])/c_Cm;
3715 
3716 								if (RNG::Ref().chance(1, 4))
3717 									t = PT_SALT;
3718 								else
3719 									t = PT_WTRV;
3720 							}
3721 							else
3722 							{
3723 								parts[i].temp = restrict_flt(elements[t].HighTemperature - dbt, MIN_TEMP, MAX_TEMP);
3724 								s = 0;
3725 							}
3726 #else
3727 							if (RNG::Ref().chance(1, 4))
3728 								t = PT_SALT;
3729 							else
3730 								t = PT_WTRV;
3731 #endif
3732 						}
3733 						else if (t == PT_BRMT)
3734 						{
3735 							if (parts[i].ctype == PT_TUNG)
3736 							{
3737 								if (ctemph < elements[parts[i].ctype].HighTemperature)
3738 									s = 0;
3739 								else
3740 								{
3741 									t = PT_LAVA;
3742 									parts[i].type = PT_TUNG;
3743 								}
3744 							}
3745 							else if (ctemph >= elements[t].HighTemperature)
3746 								t = PT_LAVA;
3747 							else
3748 								s = 0;
3749 						}
3750 						else if (t == PT_CRMC)
3751 						{
3752 							float pres = std::max((pv[y/CELL][x/CELL]+pv[(y-2)/CELL][x/CELL]+pv[(y+2)/CELL][x/CELL]+pv[y/CELL][(x-2)/CELL]+pv[y/CELL][(x+2)/CELL])*2.0f, 0.0f);
3753 							if (ctemph < pres+elements[PT_CRMC].HighTemperature)
3754 								s = 0;
3755 							else
3756 								t = PT_LAVA;
3757 						}
3758 						else
3759 							s = 0;
3760 					}
3761 					else if (elements[t].LowTemperatureTransition > -1 && ctempl<elements[t].LowTemperature)
3762 					{
3763 						// particle type change due to low temperature
3764 #ifdef REALISTIC
3765 						float dbt = ctempl - pt;
3766 						if (elements[t].LowTemperatureTransition != PT_NUM)
3767 						{
3768 							if (platent[elements[t].LowTemperatureTransition] >= (c_heat - (elements[t].LowTemperature - dbt)*c_Cm))
3769 							{
3770 								pt = (c_heat + platent[elements[t].LowTemperatureTransition])/c_Cm;
3771 								t = elements[t].LowTemperatureTransition;
3772 							}
3773 							else
3774 							{
3775 								parts[i].temp = restrict_flt(elements[t].LowTemperature - dbt, MIN_TEMP, MAX_TEMP);
3776 								s = 0;
3777 							}
3778 						}
3779 #else
3780 						if (elements[t].LowTemperatureTransition != PT_NUM)
3781 							t = elements[t].LowTemperatureTransition;
3782 #endif
3783 						else if (t == PT_WTRV)
3784 						{
3785 							if (pt < 273.0f)
3786 								t = PT_RIME;
3787 							else
3788 								t = PT_DSTW;
3789 						}
3790 						else if (t == PT_LAVA)
3791 						{
3792 							if (parts[i].ctype>0 && parts[i].ctype<PT_NUM && parts[i].ctype!=PT_LAVA && parts[i].ctype!=PT_LAVA && elements[parts[i].ctype].Enabled)
3793 							{
3794 								if (parts[i].ctype==PT_THRM&&pt>=elements[PT_BMTL].HighTemperature)
3795 									s = 0;
3796 								else if ((parts[i].ctype==PT_VIBR || parts[i].ctype==PT_BVBR) && pt>=273.15f)
3797 									s = 0;
3798 								else if (parts[i].ctype==PT_TUNG)
3799 								{
3800 									// TUNG does its own melting in its update function, so HighTemperatureTransition is not LAVA so it won't be handled by the code for HighTemperatureTransition==PT_LAVA below
3801 									// However, the threshold is stored in HighTemperature to allow it to be changed from Lua
3802 									if (pt>=elements[parts[i].ctype].HighTemperature)
3803 										s = 0;
3804 								}
3805 								else if (parts[i].ctype == PT_CRMC)
3806 								{
3807 									float pres = std::max((pv[y/CELL][x/CELL]+pv[(y-2)/CELL][x/CELL]+pv[(y+2)/CELL][x/CELL]+pv[y/CELL][(x-2)/CELL]+pv[y/CELL][(x+2)/CELL])*2.0f, 0.0f);
3808 									if (ctemph >= pres+elements[PT_CRMC].HighTemperature)
3809 										s = 0;
3810 								}
3811 								else if (elements[parts[i].ctype].HighTemperatureTransition == PT_LAVA || parts[i].ctype == PT_HEAC)
3812 								{
3813 									if (pt >= elements[parts[i].ctype].HighTemperature)
3814 										s = 0;
3815 								}
3816 								else if (pt>=973.0f)
3817 									s = 0; // freezing point for lava with any other (not listed in ptransitions as turning into lava) ctype
3818 								if (s)
3819 								{
3820 									t = parts[i].ctype;
3821 									parts[i].ctype = PT_NONE;
3822 									if (t == PT_THRM)
3823 									{
3824 										parts[i].tmp = 0;
3825 										t = PT_BMTL;
3826 									}
3827 									if (t == PT_PLUT)
3828 									{
3829 										parts[i].tmp = 0;
3830 										t = PT_LAVA;
3831 									}
3832 								}
3833 							}
3834 							else if (pt<973.0f)
3835 								t = PT_STNE;
3836 							else
3837 								s = 0;
3838 						}
3839 						else
3840 							s = 0;
3841 					}
3842 					else
3843 						s = 0;
3844 #ifdef REALISTIC
3845 					pt = restrict_flt(pt, MIN_TEMP, MAX_TEMP);
3846 					for (j=0; j<8; j++)
3847 					{
3848 						parts[surround_hconduct[j]].temp = pt;
3849 					}
3850 #endif
3851 					if (s) // particle type change occurred
3852 					{
3853 						if (t==PT_ICEI || t==PT_LAVA || t==PT_SNOW)
3854 							parts[i].ctype = parts[i].type;
3855 						if (!(t==PT_ICEI && parts[i].ctype==PT_FRZW))
3856 							parts[i].life = 0;
3857 						if (t == PT_FIRE)
3858 						{
3859 							//hackish, if tmp isn't 0 the FIRE might turn into DSTW later
3860 							//idealy transitions should use create_part(i) but some elements rely on properties staying constant
3861 							//and I don't feel like checking each one right now
3862 							parts[i].tmp = 0;
3863 						}
3864 						if ((elements[t].Properties&TYPE_GAS) && !(elements[parts[i].type].Properties&TYPE_GAS))
3865 							pv[y/CELL][x/CELL] += 0.50f;
3866 
3867 						if (t == PT_NONE)
3868 						{
3869 							kill_part(i);
3870 							goto killed;
3871 						}
3872 						// part_change_type could refuse to change the type and kill the particle
3873 						// for example, changing type to STKM but one already exists
3874 						// we need to account for that to not cause simulation corruption issues
3875 						if (part_change_type(i,x,y,t))
3876 							goto killed;
3877 
3878 						if (t==PT_FIRE || t==PT_PLSM || t==PT_CFLM)
3879 							parts[i].life = RNG::Ref().between(120, 169);
3880 						if (t == PT_LAVA)
3881 						{
3882 							if (parts[i].ctype == PT_BRMT) parts[i].ctype = PT_BMTL;
3883 							else if (parts[i].ctype == PT_SAND) parts[i].ctype = PT_GLAS;
3884 							else if (parts[i].ctype == PT_BGLA) parts[i].ctype = PT_GLAS;
3885 							else if (parts[i].ctype == PT_PQRT) parts[i].ctype = PT_QRTZ;
3886 							parts[i].life = RNG::Ref().between(240, 359);
3887 						}
3888 						transitionOccurred = true;
3889 					}
3890 
3891 					pt = parts[i].temp = restrict_flt(parts[i].temp, MIN_TEMP, MAX_TEMP);
3892 					if (t == PT_LAVA)
3893 					{
3894 						parts[i].life = restrict_flt((parts[i].temp-700)/7, 0.0f, 400.0f);
3895 						if (parts[i].ctype==PT_THRM&&parts[i].tmp>0)
3896 						{
3897 							parts[i].tmp--;
3898 							parts[i].temp = 3500;
3899 						}
3900 						if (parts[i].ctype==PT_PLUT&&parts[i].tmp>0)
3901 						{
3902 							parts[i].tmp--;
3903 							parts[i].temp = MAX_TEMP;
3904 						}
3905 					}
3906 				}
3907 				else
3908 				{
3909 					if (!(air->bmap_blockairh[y/CELL][x/CELL]&0x8))
3910 						air->bmap_blockairh[y/CELL][x/CELL]++;
3911 					parts[i].temp = restrict_flt(parts[i].temp, MIN_TEMP, MAX_TEMP);
3912 				}
3913 			}
3914 
3915 			if (t==PT_LIFE)
3916 			{
3917 				parts[i].temp = restrict_flt(parts[i].temp-50.0f, MIN_TEMP, MAX_TEMP);
3918 			}
3919 			if (t==PT_WIRE)
3920 			{
3921 				//wire_placed = 1;
3922 			}
3923 			//spark updates from walls
3924 			if ((elements[t].Properties&PROP_CONDUCTS) || t==PT_SPRK)
3925 			{
3926 				nx = x % CELL;
3927 				if (nx == 0)
3928 					nx = x/CELL - 1;
3929 				else if (nx == CELL-1)
3930 					nx = x/CELL + 1;
3931 				else
3932 					nx = x/CELL;
3933 				ny = y % CELL;
3934 				if (ny == 0)
3935 					ny = y/CELL - 1;
3936 				else if (ny == CELL-1)
3937 					ny = y/CELL + 1;
3938 				else
3939 					ny = y/CELL;
3940 				if (nx>=0 && ny>=0 && nx<XRES/CELL && ny<YRES/CELL)
3941 				{
3942 					if (t!=PT_SPRK)
3943 					{
3944 						if (emap[ny][nx]==12 && !parts[i].life && bmap[ny][nx] != WL_STASIS)
3945 						{
3946 							part_change_type(i,x,y,PT_SPRK);
3947 							parts[i].life = 4;
3948 							parts[i].ctype = t;
3949 							t = PT_SPRK;
3950 						}
3951 					}
3952 					else if (bmap[ny][nx]==WL_DETECT || bmap[ny][nx]==WL_EWALL || bmap[ny][nx]==WL_ALLOWLIQUID || bmap[ny][nx]==WL_WALLELEC || bmap[ny][nx]==WL_ALLOWALLELEC || bmap[ny][nx]==WL_EHOLE)
3953 						set_emap(nx, ny);
3954 				}
3955 			}
3956 
3957 			//the basic explosion, from the .explosive variable
3958 			if ((elements[t].Explosive&2) && pv[y/CELL][x/CELL]>2.5f)
3959 			{
3960 				parts[i].life = RNG::Ref().between(180, 259);
3961 				parts[i].temp = restrict_flt(elements[PT_FIRE].DefaultProperties.temp + (elements[t].Flammable/2), MIN_TEMP, MAX_TEMP);
3962 				t = PT_FIRE;
3963 				part_change_type(i,x,y,t);
3964 				pv[y/CELL][x/CELL] += 0.25f * CFDS;
3965 			}
3966 
3967 
3968 			s = 1;
3969 			gravtot = fabs(gravy[(y/CELL)*(XRES/CELL)+(x/CELL)])+fabs(gravx[(y/CELL)*(XRES/CELL)+(x/CELL)]);
3970 			if (elements[t].HighPressureTransition>-1 && pv[y/CELL][x/CELL]>elements[t].HighPressure) {
3971 				// particle type change due to high pressure
3972 				if (elements[t].HighPressureTransition!=PT_NUM)
3973 					t = elements[t].HighPressureTransition;
3974 				else if (t==PT_BMTL) {
3975 					if (pv[y/CELL][x/CELL]>2.5f)
3976 						t = PT_BRMT;
3977 					else if (pv[y/CELL][x/CELL]>1.0f && parts[i].tmp==1)
3978 						t = PT_BRMT;
3979 					else s = 0;
3980 				}
3981 				else s = 0;
3982 			} else if (elements[t].LowPressureTransition>-1 && pv[y/CELL][x/CELL]<elements[t].LowPressure && gravtot<=(elements[t].LowPressure/4.0f)) {
3983 				// particle type change due to low pressure
3984 				if (elements[t].LowPressureTransition!=PT_NUM)
3985 					t = elements[t].LowPressureTransition;
3986 				else s = 0;
3987 			} else if (elements[t].HighPressureTransition>-1 && gravtot>(elements[t].HighPressure/4.0f)) {
3988 				// particle type change due to high gravity
3989 				if (elements[t].HighPressureTransition!=PT_NUM)
3990 					t = elements[t].HighPressureTransition;
3991 				else if (t==PT_BMTL) {
3992 					if (gravtot>0.625f)
3993 						t = PT_BRMT;
3994 					else if (gravtot>0.25f && parts[i].tmp==1)
3995 						t = PT_BRMT;
3996 					else s = 0;
3997 				}
3998 				else s = 0;
3999 			} else s = 0;
4000 
4001 			// particle type change occurred
4002 			if (s)
4003 			{
4004 				if (t == PT_NONE)
4005 				{
4006 					kill_part(i);
4007 					goto killed;
4008 				}
4009 				parts[i].life = 0;
4010 				// part_change_type could refuse to change the type and kill the particle
4011 				// for example, changing type to STKM but one already exists
4012 				// we need to account for that to not cause simulation corruption issues
4013 				if (part_change_type(i,x,y,t))
4014 					goto killed;
4015 				if (t == PT_FIRE)
4016 					parts[i].life = RNG::Ref().between(120, 169);
4017 				transitionOccurred = true;
4018 			}
4019 
4020 			//call the particle update function, if there is one
4021 #if !defined(RENDERER) && defined(LUACONSOLE)
4022 			if (lua_el_mode[parts[i].type] == 3)
4023 			{
4024 				if (luacon_elementReplacement(this, i, x, y, surround_space, nt, parts, pmap) || t != parts[i].type)
4025 					continue;
4026 				// Need to update variables, in case they've been changed by Lua
4027 				x = (int)(parts[i].x+0.5f);
4028 				y = (int)(parts[i].y+0.5f);
4029 			}
4030 
4031 			if (elements[t].Update && lua_el_mode[t] != 2)
4032 #else
4033 			if (elements[t].Update)
4034 #endif
4035 			{
4036 				if ((*(elements[t].Update))(this, i, x, y, surround_space, nt, parts, pmap))
4037 					continue;
4038 				else if (t==PT_WARP)
4039 				{
4040 					// Warp does some movement in its update func, update variables to avoid incorrect data in pmap
4041 					x = (int)(parts[i].x+0.5f);
4042 					y = (int)(parts[i].y+0.5f);
4043 				}
4044 			}
4045 #if !defined(RENDERER) && defined(LUACONSOLE)
4046 			if (lua_el_mode[parts[i].type] && lua_el_mode[parts[i].type] != 3)
4047 			{
4048 				if (luacon_elementReplacement(this, i, x, y, surround_space, nt, parts, pmap) || t != parts[i].type)
4049 					continue;
4050 				// Need to update variables, in case they've been changed by Lua
4051 				x = (int)(parts[i].x+0.5f);
4052 				y = (int)(parts[i].y+0.5f);
4053 			}
4054 #endif
4055 
4056 			if(legacy_enable)//if heat sim is off
4057 				Element::legacyUpdate(this, i,x,y,surround_space,nt, parts, pmap);
4058 
4059 killed:
4060 			if (parts[i].type == PT_NONE)//if its dead, skip to next particle
4061 				continue;
4062 
4063 			if (transitionOccurred)
4064 				continue;
4065 
4066 			if (!parts[i].vx&&!parts[i].vy)//if its not moving, skip to next particle, movement code it next
4067 				continue;
4068 
4069 			mv = fmaxf(fabsf(parts[i].vx), fabsf(parts[i].vy));
4070 			if (mv < ISTP)
4071 			{
4072 				clear_x = x;
4073 				clear_y = y;
4074 				clear_xf = parts[i].x;
4075 				clear_yf = parts[i].y;
4076 				fin_xf = clear_xf + parts[i].vx;
4077 				fin_yf = clear_yf + parts[i].vy;
4078 				fin_x = (int)(fin_xf+0.5f);
4079 				fin_y = (int)(fin_yf+0.5f);
4080 			}
4081 			else
4082 			{
4083 				if (mv > SIM_MAXVELOCITY)
4084 				{
4085 					parts[i].vx *= SIM_MAXVELOCITY/mv;
4086 					parts[i].vy *= SIM_MAXVELOCITY/mv;
4087 					mv = SIM_MAXVELOCITY;
4088 				}
4089 				// interpolate to see if there is anything in the way
4090 				dx = parts[i].vx*ISTP/mv;
4091 				dy = parts[i].vy*ISTP/mv;
4092 				fin_xf = parts[i].x;
4093 				fin_yf = parts[i].y;
4094 				fin_x = (int)(fin_xf+0.5f);
4095 				fin_y = (int)(fin_yf+0.5f);
4096 				bool closedEholeStart = this->InBounds(fin_x, fin_y) && (bmap[fin_y/CELL][fin_x/CELL] == WL_EHOLE && !emap[fin_y/CELL][fin_x/CELL]);
4097 				while (1)
4098 				{
4099 					mv -= ISTP;
4100 					fin_xf += dx;
4101 					fin_yf += dy;
4102 					fin_x = (int)(fin_xf+0.5f);
4103 					fin_y = (int)(fin_yf+0.5f);
4104 					if (edgeMode == 2)
4105 					{
4106 						bool x_ok = (fin_xf >= CELL-.5f && fin_xf < XRES-CELL-.5f);
4107 						bool y_ok = (fin_yf >= CELL-.5f && fin_yf < YRES-CELL-.5f);
4108 						if (!x_ok)
4109 							fin_xf = remainder_p(fin_xf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f;
4110 						if (!y_ok)
4111 							fin_yf = remainder_p(fin_yf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f;
4112 						fin_x = (int)(fin_xf+0.5f);
4113 						fin_y = (int)(fin_yf+0.5f);
4114 					}
4115 					if (mv <= 0.0f)
4116 					{
4117 						// nothing found
4118 						fin_xf = parts[i].x + parts[i].vx;
4119 						fin_yf = parts[i].y + parts[i].vy;
4120 						if (edgeMode == 2)
4121 						{
4122 							bool x_ok = (fin_xf >= CELL-.5f && fin_xf < XRES-CELL-.5f);
4123 							bool y_ok = (fin_yf >= CELL-.5f && fin_yf < YRES-CELL-.5f);
4124 							if (!x_ok)
4125 								fin_xf = remainder_p(fin_xf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f;
4126 							if (!y_ok)
4127 								fin_yf = remainder_p(fin_yf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f;
4128 						}
4129 						fin_x = (int)(fin_xf+0.5f);
4130 						fin_y = (int)(fin_yf+0.5f);
4131 						clear_xf = fin_xf-dx;
4132 						clear_yf = fin_yf-dy;
4133 						clear_x = (int)(clear_xf+0.5f);
4134 						clear_y = (int)(clear_yf+0.5f);
4135 						break;
4136 					}
4137 					//block if particle can't move (0), or some special cases where it returns 1 (can_move = 3 but returns 1 meaning particle will be eaten)
4138 					//also photons are still blocked (slowed down) by any particle (even ones it can move through), and absorb wall also blocks particles
4139 					int eval = eval_move(t, fin_x, fin_y, NULL);
4140 					if (!eval || (can_move[t][TYP(pmap[fin_y][fin_x])] == 3 && eval == 1) || (t == PT_PHOT && pmap[fin_y][fin_x]) || bmap[fin_y/CELL][fin_x/CELL]==WL_DESTROYALL || closedEholeStart!=(bmap[fin_y/CELL][fin_x/CELL] == WL_EHOLE && !emap[fin_y/CELL][fin_x/CELL]))
4141 					{
4142 						// found an obstacle
4143 						clear_xf = fin_xf-dx;
4144 						clear_yf = fin_yf-dy;
4145 						clear_x = (int)(clear_xf+0.5f);
4146 						clear_y = (int)(clear_yf+0.5f);
4147 						break;
4148 					}
4149 					if (bmap[fin_y/CELL][fin_x/CELL]==WL_DETECT && emap[fin_y/CELL][fin_x/CELL]<8)
4150 						set_emap(fin_x/CELL, fin_y/CELL);
4151 				}
4152 			}
4153 
4154 			stagnant = parts[i].flags & FLAG_STAGNANT;
4155 			parts[i].flags &= ~FLAG_STAGNANT;
4156 
4157 			if (t==PT_STKM || t==PT_STKM2 || t==PT_FIGH)
4158 			{
4159 				//head movement, let head pass through anything
4160 				parts[i].x += parts[i].vx;
4161 				parts[i].y += parts[i].vy;
4162 				int nx = (int)((float)parts[i].x+0.5f);
4163 				int ny = (int)((float)parts[i].y+0.5f);
4164 				if (edgeMode == 2)
4165 				{
4166 					bool x_ok = (nx >= CELL && nx < XRES-CELL);
4167 					bool y_ok = (ny >= CELL && ny < YRES-CELL);
4168 					int oldnx = nx, oldny = ny;
4169 					if (!x_ok)
4170 					{
4171 						parts[i].x = remainder_p(parts[i].x-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f;
4172 						nx = (int)((float)parts[i].x+0.5f);
4173 					}
4174 					if (!y_ok)
4175 					{
4176 						parts[i].y = remainder_p(parts[i].y-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f;
4177 						ny = (int)((float)parts[i].y+0.5f);
4178 					}
4179 
4180 					if (!x_ok || !y_ok) //when moving from left to right stickmen might be able to fall through solid things, fix with "eval_move(t, nx+diffx, ny+diffy, NULL)" but then they die instead
4181 					{
4182 						//adjust stickmen legs
4183 						playerst* stickman = NULL;
4184 						int t = parts[i].type;
4185 						if (t == PT_STKM)
4186 							stickman = &player;
4187 						else if (t == PT_STKM2)
4188 							stickman = &player2;
4189 						else if (t == PT_FIGH && parts[i].tmp >= 0 && parts[i].tmp < MAX_FIGHTERS)
4190 							stickman = &fighters[parts[i].tmp];
4191 
4192 						if (stickman)
4193 							for (int i = 0; i < 16; i+=2)
4194 							{
4195 								stickman->legs[i] += (nx-oldnx);
4196 								stickman->legs[i+1] += (ny-oldny);
4197 								stickman->accs[i/2] *= .95f;
4198 							}
4199 						parts[i].vy *= .95f;
4200 						parts[i].vx *= .95f;
4201 					}
4202 				}
4203 				if (ny!=y || nx!=x)
4204 				{
4205 					if (ID(pmap[y][x]) == i)
4206 						pmap[y][x] = 0;
4207 					else if (ID(photons[y][x]) == i)
4208 						photons[y][x] = 0;
4209 					if (nx<CELL || nx>=XRES-CELL || ny<CELL || ny>=YRES-CELL)
4210 					{
4211 						kill_part(i);
4212 						continue;
4213 					}
4214 					if (elements[t].Properties & TYPE_ENERGY)
4215 						photons[ny][nx] = PMAP(i, t);
4216 					else if (t)
4217 						pmap[ny][nx] = PMAP(i, t);
4218 				}
4219 			}
4220 			else if (elements[t].Properties & TYPE_ENERGY)
4221 			{
4222 				if (t == PT_PHOT)
4223 				{
4224 					if (parts[i].flags&FLAG_SKIPMOVE)
4225 					{
4226 						parts[i].flags &= ~FLAG_SKIPMOVE;
4227 						continue;
4228 					}
4229 
4230 					if (eval_move(PT_PHOT, fin_x, fin_y, NULL))
4231 					{
4232 						int rt = TYP(pmap[fin_y][fin_x]);
4233 						int lt = TYP(pmap[y][x]);
4234 						int rt_glas = (rt == PT_GLAS) || (rt == PT_BGLA);
4235 						int lt_glas = (lt == PT_GLAS) || (lt == PT_BGLA);
4236 						if ((rt_glas && !lt_glas) || (lt_glas && !rt_glas))
4237 						{
4238 							if (!get_normal_interp(REFRACT|t, parts[i].x, parts[i].y, parts[i].vx, parts[i].vy, &nrx, &nry)) {
4239 								kill_part(i);
4240 								continue;
4241 							}
4242 
4243 							r = get_wavelength_bin(&parts[i].ctype);
4244 							if (r == -1 || !(parts[i].ctype&0x3FFFFFFF))
4245 							{
4246 								kill_part(i);
4247 								continue;
4248 							}
4249 							nn = GLASS_IOR - GLASS_DISP*(r-30)/30.0f;
4250 							nn *= nn;
4251 							nrx = -nrx;
4252 							nry = -nry;
4253 							if (rt_glas && !lt_glas)
4254 								nn = 1.0f/nn;
4255 							ct1 = parts[i].vx*nrx + parts[i].vy*nry;
4256 							ct2 = 1.0f - (nn*nn)*(1.0f-(ct1*ct1));
4257 							if (ct2 < 0.0f) {
4258 								// total internal reflection
4259 								parts[i].vx -= 2.0f*ct1*nrx;
4260 								parts[i].vy -= 2.0f*ct1*nry;
4261 								fin_xf = parts[i].x;
4262 								fin_yf = parts[i].y;
4263 								fin_x = x;
4264 								fin_y = y;
4265 							} else {
4266 								// refraction
4267 								ct2 = sqrtf(ct2);
4268 								ct2 = ct2 - nn*ct1;
4269 								parts[i].vx = nn*parts[i].vx + ct2*nrx;
4270 								parts[i].vy = nn*parts[i].vy + ct2*nry;
4271 							}
4272 						}
4273 					}
4274 				}
4275 				if (stagnant)//FLAG_STAGNANT set, was reflected on previous frame
4276 				{
4277 					// cast coords as int then back to float for compatibility with existing saves
4278 					if (!do_move(i, x, y, (float)fin_x, (float)fin_y) && parts[i].type) {
4279 						kill_part(i);
4280 						continue;
4281 					}
4282 				}
4283 				else if (!do_move(i, x, y, fin_xf, fin_yf))
4284 				{
4285 					if (parts[i].type == PT_NONE)
4286 						continue;
4287 					// reflection
4288 					parts[i].flags |= FLAG_STAGNANT;
4289 					if (t==PT_NEUT && RNG::Ref().chance(1, 10))
4290 					{
4291 						kill_part(i);
4292 						continue;
4293 					}
4294 					r = pmap[fin_y][fin_x];
4295 
4296 					if ((TYP(r)==PT_PIPE || TYP(r) == PT_PPIP) && !TYP(parts[ID(r)].ctype))
4297 					{
4298 						parts[ID(r)].ctype =  parts[i].type;
4299 						parts[ID(r)].temp = parts[i].temp;
4300 						parts[ID(r)].tmp2 = parts[i].life;
4301 						parts[ID(r)].pavg[0] = parts[i].tmp;
4302 						parts[ID(r)].pavg[1] = parts[i].ctype;
4303 						kill_part(i);
4304 						continue;
4305 					}
4306 
4307 					if (TYP(r))
4308 						parts[i].ctype &= elements[TYP(r)].PhotonReflectWavelengths;
4309 
4310 					if (get_normal_interp(t, parts[i].x, parts[i].y, parts[i].vx, parts[i].vy, &nrx, &nry))
4311 					{
4312 						if (TYP(r) == PT_CRMC)
4313 						{
4314 							float r = RNG::Ref().between(-50, 50) * 0.01f, rx, ry, anrx, anry;
4315 							r = r * r * r;
4316 							rx = cosf(r); ry = sinf(r);
4317 							anrx = rx * nrx + ry * nry;
4318 							anry = rx * nry - ry * nrx;
4319 							dp = anrx*parts[i].vx + anry*parts[i].vy;
4320 							parts[i].vx -= 2.0f*dp*anrx;
4321 							parts[i].vy -= 2.0f*dp*anry;
4322 						}
4323 						else
4324 						{
4325 							dp = nrx*parts[i].vx + nry*parts[i].vy;
4326 							parts[i].vx -= 2.0f*dp*nrx;
4327 							parts[i].vy -= 2.0f*dp*nry;
4328 						}
4329 						// leave the actual movement until next frame so that reflection of fast particles and refraction happen correctly
4330 					}
4331 					else
4332 					{
4333 						if (t!=PT_NEUT)
4334 							kill_part(i);
4335 						continue;
4336 					}
4337 					if (!(parts[i].ctype&0x3FFFFFFF) && t == PT_PHOT)
4338 					{
4339 						kill_part(i);
4340 						continue;
4341 					}
4342 				}
4343 			}
4344 			else if (elements[t].Falldown==0)
4345 			{
4346 				// gasses and solids (but not powders)
4347 				if (!do_move(i, x, y, fin_xf, fin_yf))
4348 				{
4349 					if (parts[i].type == PT_NONE)
4350 						continue;
4351 					// can't move there, so bounce off
4352 					// TODO
4353 					// TODO: Work out what previous TODO was for
4354 					if (fin_x>x+ISTP) fin_x=x+ISTP;
4355 					if (fin_x<x-ISTP) fin_x=x-ISTP;
4356 					if (fin_y>y+ISTP) fin_y=y+ISTP;
4357 					if (fin_y<y-ISTP) fin_y=y-ISTP;
4358 					if (do_move(i, x, y, 0.25f+(float)(2*x-fin_x), 0.25f+fin_y))
4359 					{
4360 						parts[i].vx *= elements[t].Collision;
4361 					}
4362 					else if (do_move(i, x, y, 0.25f+fin_x, 0.25f+(float)(2*y-fin_y)))
4363 					{
4364 						parts[i].vy *= elements[t].Collision;
4365 					}
4366 					else
4367 					{
4368 						parts[i].vx *= elements[t].Collision;
4369 						parts[i].vy *= elements[t].Collision;
4370 					}
4371 				}
4372 			}
4373 			else
4374 			{
4375 				// Checking stagnant is cool, but then it doesn't update when you change it later.
4376 				if (water_equal_test && elements[t].Falldown == 2 && RNG::Ref().chance(1, 200))
4377 				{
4378 					if (!flood_water(x, y, i))
4379 						goto movedone;
4380 				}
4381 				// liquids and powders
4382 				if (!do_move(i, x, y, fin_xf, fin_yf))
4383 				{
4384 					if (parts[i].type == PT_NONE)
4385 						continue;
4386 					if (fin_x!=x && do_move(i, x, y, fin_xf, clear_yf))
4387 					{
4388 						parts[i].vx *= elements[t].Collision;
4389 						parts[i].vy *= elements[t].Collision;
4390 					}
4391 					else if (fin_y!=y && do_move(i, x, y, clear_xf, fin_yf))
4392 					{
4393 						parts[i].vx *= elements[t].Collision;
4394 						parts[i].vy *= elements[t].Collision;
4395 					}
4396 					else
4397 					{
4398 						s = 1;
4399 						r = RNG::Ref().between(0, 1) * 2 - 1;// position search direction (left/right first)
4400 						if ((clear_x!=x || clear_y!=y || nt || surround_space) &&
4401 							(fabsf(parts[i].vx)>0.01f || fabsf(parts[i].vy)>0.01f))
4402 						{
4403 							// allow diagonal movement if target position is blocked
4404 							// but no point trying this if particle is stuck in a block of identical particles
4405 							dx = parts[i].vx - parts[i].vy*r;
4406 							dy = parts[i].vy + parts[i].vx*r;
4407 							if (fabsf(dy)>fabsf(dx))
4408 								mv = fabsf(dy);
4409 							else
4410 								mv = fabsf(dx);
4411 							dx /= mv;
4412 							dy /= mv;
4413 							if (do_move(i, x, y, clear_xf+dx, clear_yf+dy))
4414 							{
4415 								parts[i].vx *= elements[t].Collision;
4416 								parts[i].vy *= elements[t].Collision;
4417 								goto movedone;
4418 							}
4419 							swappage = dx;
4420 							dx = dy*r;
4421 							dy = -swappage*r;
4422 							if (do_move(i, x, y, clear_xf+dx, clear_yf+dy))
4423 							{
4424 								parts[i].vx *= elements[t].Collision;
4425 								parts[i].vy *= elements[t].Collision;
4426 								goto movedone;
4427 							}
4428 						}
4429 						if (elements[t].Falldown>1 && !grav->IsEnabled() && gravityMode==0 && parts[i].vy>fabsf(parts[i].vx))
4430 						{
4431 							s = 0;
4432 							// stagnant is true if FLAG_STAGNANT was set for this particle in previous frame
4433 							if (!stagnant || nt) //nt is if there is an something else besides the current particle type, around the particle
4434 								rt = 30;//slight less water lag, although it changes how it moves a lot
4435 							else
4436 								rt = 10;
4437 
4438 							if (t==PT_GEL)
4439 								rt = parts[i].tmp*0.20f+5.0f;
4440 
4441 							for (j=clear_x+r; j>=0 && j>=clear_x-rt && j<clear_x+rt && j<XRES; j+=r)
4442 							{
4443 								if ((TYP(pmap[fin_y][j])!=t || bmap[fin_y/CELL][j/CELL])
4444 									&& (s=do_move(i, x, y, (float)j, fin_yf)))
4445 								{
4446 									nx = (int)(parts[i].x+0.5f);
4447 									ny = (int)(parts[i].y+0.5f);
4448 									break;
4449 								}
4450 								if (fin_y!=clear_y && (TYP(pmap[clear_y][j])!=t || bmap[clear_y/CELL][j/CELL])
4451 									&& (s=do_move(i, x, y, (float)j, clear_yf)))
4452 								{
4453 									nx = (int)(parts[i].x+0.5f);
4454 									ny = (int)(parts[i].y+0.5f);
4455 									break;
4456 								}
4457 								if (TYP(pmap[clear_y][j])!=t || (bmap[clear_y/CELL][j/CELL] && bmap[clear_y/CELL][j/CELL]!=WL_STREAM))
4458 									break;
4459 							}
4460 							if (parts[i].vy>0)
4461 								r = 1;
4462 							else
4463 								r = -1;
4464 							if (s==1)
4465 								for (j=ny+r; j>=0 && j<YRES && j>=ny-rt && j<ny+rt; j+=r)
4466 								{
4467 									if ((TYP(pmap[j][nx])!=t || bmap[j/CELL][nx/CELL]) && do_move(i, nx, ny, (float)nx, (float)j))
4468 										break;
4469 									if (TYP(pmap[j][nx])!=t || (bmap[j/CELL][nx/CELL] && bmap[j/CELL][nx/CELL]!=WL_STREAM))
4470 										break;
4471 								}
4472 							else if (s==-1) {} // particle is out of bounds
4473 							else if ((clear_x!=x||clear_y!=y) && do_move(i, x, y, clear_xf, clear_yf)) {}
4474 							else parts[i].flags |= FLAG_STAGNANT;
4475 							parts[i].vx *= elements[t].Collision;
4476 							parts[i].vy *= elements[t].Collision;
4477 						}
4478 						else if (elements[t].Falldown>1 && fabsf(pGravX*parts[i].vx+pGravY*parts[i].vy)>fabsf(pGravY*parts[i].vx-pGravX*parts[i].vy))
4479 						{
4480 							float nxf, nyf, prev_pGravX, prev_pGravY, ptGrav = elements[t].Gravity;
4481 							s = 0;
4482 							// stagnant is true if FLAG_STAGNANT was set for this particle in previous frame
4483 							if (!stagnant || nt) //nt is if there is an something else besides the current particle type, around the particle
4484 								rt = 30;//slight less water lag, although it changes how it moves a lot
4485 							else
4486 								rt = 10;
4487 							// clear_xf, clear_yf is the last known position that the particle should almost certainly be able to move to
4488 							nxf = clear_xf;
4489 							nyf = clear_yf;
4490 							nx = clear_x;
4491 							ny = clear_y;
4492 							// Look for spaces to move horizontally (perpendicular to gravity direction), keep going until a space is found or the number of positions examined = rt
4493 							for (j=0;j<rt;j++)
4494 							{
4495 								// Calculate overall gravity direction
4496 								switch (gravityMode)
4497 								{
4498 									default:
4499 									case 0:
4500 										pGravX = 0.0f;
4501 										pGravY = ptGrav;
4502 										break;
4503 									case 1:
4504 										pGravX = pGravY = 0.0f;
4505 										break;
4506 									case 2:
4507 										pGravD = 0.01f - hypotf((nx - XCNTR), (ny - YCNTR));
4508 										pGravX = ptGrav * ((float)(nx - XCNTR) / pGravD);
4509 										pGravY = ptGrav * ((float)(ny - YCNTR) / pGravD);
4510 										break;
4511 								}
4512 								pGravX += gravx[(ny/CELL)*(XRES/CELL)+(nx/CELL)];
4513 								pGravY += gravy[(ny/CELL)*(XRES/CELL)+(nx/CELL)];
4514 								// Scale gravity vector so that the largest component is 1 pixel
4515 								if (fabsf(pGravY)>fabsf(pGravX))
4516 									mv = fabsf(pGravY);
4517 								else
4518 									mv = fabsf(pGravX);
4519 								if (mv<0.0001f) break;
4520 								pGravX /= mv;
4521 								pGravY /= mv;
4522 								// Move 1 pixel perpendicularly to gravity
4523 								// r is +1/-1, to try moving left or right at random
4524 								if (j)
4525 								{
4526 									// Not quite the gravity direction
4527 									// Gravity direction + last change in gravity direction
4528 									// This makes liquid movement a bit less frothy, particularly for balls of liquid in radial gravity. With radial gravity, instead of just moving along a tangent, the attempted movement will follow the curvature a bit better.
4529 									nxf += r*(pGravY*2.0f-prev_pGravY);
4530 									nyf += -r*(pGravX*2.0f-prev_pGravX);
4531 								}
4532 								else
4533 								{
4534 									nxf += r*pGravY;
4535 									nyf += -r*pGravX;
4536 								}
4537 								prev_pGravX = pGravX;
4538 								prev_pGravY = pGravY;
4539 								// Check whether movement is allowed
4540 								nx = (int)(nxf+0.5f);
4541 								ny = (int)(nyf+0.5f);
4542 								if (nx<0 || ny<0 || nx>=XRES || ny >=YRES)
4543 									break;
4544 								if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL])
4545 								{
4546 									s = do_move(i, x, y, nxf, nyf);
4547 									if (s)
4548 									{
4549 										// Movement was successful
4550 										nx = (int)(parts[i].x+0.5f);
4551 										ny = (int)(parts[i].y+0.5f);
4552 										break;
4553 									}
4554 									// A particle of a different type, or a wall, was found. Stop trying to move any further horizontally unless the wall should be completely invisible to particles.
4555 									if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]!=WL_STREAM)
4556 										break;
4557 								}
4558 							}
4559 							if (s==1)
4560 							{
4561 								// The particle managed to move horizontally, now try to move vertically (parallel to gravity direction)
4562 								// Keep going until the particle is blocked (by something that isn't the same element) or the number of positions examined = rt
4563 								clear_x = nx;
4564 								clear_y = ny;
4565 								for (j=0;j<rt;j++)
4566 								{
4567 									// Calculate overall gravity direction
4568 									switch (gravityMode)
4569 									{
4570 										default:
4571 										case 0:
4572 											pGravX = 0.0f;
4573 											pGravY = ptGrav;
4574 											break;
4575 										case 1:
4576 											pGravX = pGravY = 0.0f;
4577 											break;
4578 										case 2:
4579 											pGravD = 0.01f - hypotf((nx - XCNTR), (ny - YCNTR));
4580 											pGravX = ptGrav * ((float)(nx - XCNTR) / pGravD);
4581 											pGravY = ptGrav * ((float)(ny - YCNTR) / pGravD);
4582 											break;
4583 									}
4584 									pGravX += gravx[(ny/CELL)*(XRES/CELL)+(nx/CELL)];
4585 									pGravY += gravy[(ny/CELL)*(XRES/CELL)+(nx/CELL)];
4586 									// Scale gravity vector so that the largest component is 1 pixel
4587 									if (fabsf(pGravY)>fabsf(pGravX))
4588 										mv = fabsf(pGravY);
4589 									else
4590 										mv = fabsf(pGravX);
4591 									if (mv<0.0001f) break;
4592 									pGravX /= mv;
4593 									pGravY /= mv;
4594 									// Move 1 pixel in the direction of gravity
4595 									nxf += pGravX;
4596 									nyf += pGravY;
4597 									nx = (int)(nxf+0.5f);
4598 									ny = (int)(nyf+0.5f);
4599 									if (nx<0 || ny<0 || nx>=XRES || ny>=YRES)
4600 										break;
4601 									// If the space is anything except the same element (a wall, empty space, or occupied by a particle of a different element), try to move into it
4602 									if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL])
4603 									{
4604 										s = do_move(i, clear_x, clear_y, nxf, nyf);
4605 										if (s || TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]!=WL_STREAM)
4606 											break; // found the edge of the liquid and movement into it succeeded, so stop moving down
4607 									}
4608 								}
4609 							}
4610 							else if (s==-1) {} // particle is out of bounds
4611 							else if ((clear_x!=x||clear_y!=y) && do_move(i, x, y, clear_xf, clear_yf)) {} // try moving to the last clear position
4612 							else parts[i].flags |= FLAG_STAGNANT;
4613 							parts[i].vx *= elements[t].Collision;
4614 							parts[i].vy *= elements[t].Collision;
4615 						}
4616 						else
4617 						{
4618 							// if interpolation was done, try moving to last clear position
4619 							if ((clear_x!=x||clear_y!=y) && do_move(i, x, y, clear_xf, clear_yf)) {}
4620 							else parts[i].flags |= FLAG_STAGNANT;
4621 							parts[i].vx *= elements[t].Collision;
4622 							parts[i].vy *= elements[t].Collision;
4623 						}
4624 					}
4625 				}
4626 			}
4627 movedone:
4628 			continue;
4629 		}
4630 
4631 	//'f' was pressed (single frame)
4632 	if (framerender)
4633 		framerender--;
4634 }
4635 
GetParticleType(ByteString type)4636 int Simulation::GetParticleType(ByteString type)
4637 {
4638 	char * txt = (char*)type.c_str();
4639 
4640 	// alternative names for some elements
4641 	if (!strcasecmp(txt, "C4"))
4642 		return PT_PLEX;
4643 	else if (!strcasecmp(txt, "C5"))
4644 		return PT_C5;
4645 	else if (!strcasecmp(txt, "NONE"))
4646 		return PT_NONE;
4647 	for (int i = 1; i < PT_NUM; i++)
4648 	{
4649 		if (!strcasecmp(txt, elements[i].Name.ToUtf8().c_str()) && elements[i].Name.size() && elements[i].Enabled)
4650 		{
4651 			return i;
4652 		}
4653 	}
4654 	return -1;
4655 }
4656 
SimulateGoL()4657 void Simulation::SimulateGoL()
4658 {
4659 	CGOL = 0;
4660 	//TODO: maybe this should only loop through active particles
4661 	for (int ny = CELL; ny < YRES-CELL; ny++)
4662 	{
4663 		//go through every particle and set neighbor map
4664 		for (int nx = CELL; nx < XRES-CELL; nx++)
4665 		{
4666 			int r = pmap[ny][nx];
4667 			if (!r)
4668 			{
4669 				gol[ny][nx] = 0;
4670 				continue;
4671 			}
4672 			if (TYP(r) == PT_LIFE)
4673 			{
4674 				int golnum = parts[ID(r)].ctype + 1;
4675 				if (golnum <= 0 || golnum > NGOL)
4676 				{
4677 					kill_part(ID(r));
4678 					continue;
4679 				}
4680 				gol[ny][nx] = golnum;
4681 				if (parts[ID(r)].tmp == grule[golnum][9]-1)
4682 				{
4683 					for (int nnx = -1; nnx < 2; nnx++)
4684 					{
4685 						//it will count itself as its own neighbor, which is needed, but will have 1 extra for delete check
4686 						for (int nny = -1; nny < 2; nny++)
4687 						{
4688 							int adx = ((nx+nnx+XRES-3*CELL)%(XRES-2*CELL))+CELL;
4689 							int ady = ((ny+nny+YRES-3*CELL)%(YRES-2*CELL))+CELL;
4690 							int rt = pmap[ady][adx];
4691 							if (!rt || TYP(rt) == PT_LIFE)
4692 							{
4693 								//the total neighbor count is in 0
4694 								gol2[ady][adx][0] ++;
4695 								//insert golnum into neighbor table
4696 								for (int i = 1; i < 9; i++)
4697 								{
4698 									if (!gol2[ady][adx][i])
4699 									{
4700 										gol2[ady][adx][i] = (golnum<<4)+1;
4701 										break;
4702 									}
4703 									else if((gol2[ady][adx][i]>>4)==golnum)
4704 									{
4705 										gol2[ady][adx][i]++;
4706 										break;
4707 									}
4708 								}
4709 							}
4710 						}
4711 					}
4712 				}
4713 				else
4714 				{
4715 					parts[ID(r)].tmp --;
4716 				}
4717 			}
4718 		}
4719 	}
4720 	for (int ny = CELL; ny < YRES-CELL; ny++)
4721 	{
4722 		//go through every particle again, but check neighbor map, then update particles
4723 		for (int nx = CELL; nx < XRES-CELL; nx++)
4724 		{
4725 			int r = pmap[ny][nx];
4726 			if (r && TYP(r)!=PT_LIFE)
4727 				continue;
4728 			int neighbors = gol2[ny][nx][0];
4729 			if (neighbors)
4730 			{
4731 				if (!(bmap[ny/CELL][nx/CELL] == WL_STASIS && emap[ny/CELL][nx/CELL] < 8))
4732 				{
4733 					int golnum = gol[ny][nx];
4734 					if (!r)
4735 					{
4736 						//Find which type we can try and create
4737 						int creategol = 0xFF;
4738 						for (int i = 1; i < 9; i++)
4739 						{
4740 							if (!gol2[ny][nx][i]) break;
4741 							golnum = (gol2[ny][nx][i]>>4);
4742 							if (grule[golnum][neighbors]>= 2 && (gol2[ny][nx][i]&0xF) >= (neighbors%2)+neighbors/2)
4743 							{
4744 								if (golnum < creategol)
4745 									creategol = golnum;
4746 							}
4747 						}
4748 						if (creategol < 0xFF)
4749 							create_part(-1, nx, ny, PT_LIFE, creategol-1);
4750 					}
4751 					else if (grule[golnum][neighbors-1] == 0 || grule[golnum][neighbors-1] == 2)//subtract 1 because it counted itself
4752 					{
4753 						if (parts[ID(r)].tmp == grule[golnum][9]-1)
4754 							parts[ID(r)].tmp--;
4755 					}
4756 				}
4757 				for (int z = 0; z < 9; z++)
4758 					gol2[ny][nx][z] = 0;//this improves performance A LOT compared to the memset, i was getting ~23 more fps with this.
4759 			}
4760 			//we still need to kill things with 0 neighbors (higher state life)
4761 			if (r && parts[ID(r)].tmp <= 0)
4762 				kill_part(ID(r));
4763 		}
4764 	}
4765 	//memset(gol2, 0, sizeof(gol2));
4766 }
4767 
RecalcFreeParticles(bool do_life_dec)4768 void Simulation::RecalcFreeParticles(bool do_life_dec)
4769 {
4770 	int x, y, t;
4771 	int lastPartUsed = 0;
4772 	int lastPartUnused = -1;
4773 
4774 	memset(pmap, 0, sizeof(pmap));
4775 	memset(pmap_count, 0, sizeof(pmap_count));
4776 	memset(photons, 0, sizeof(photons));
4777 
4778 	NUM_PARTS = 0;
4779 	//the particle loop that resets the pmap/photon maps every frame, to update them.
4780 	for (int i = 0; i <= parts_lastActiveIndex; i++)
4781 	{
4782 		if (parts[i].type)
4783 		{
4784 			t = parts[i].type;
4785 			x = (int)(parts[i].x+0.5f);
4786 			y = (int)(parts[i].y+0.5f);
4787 			bool inBounds = false;
4788 			if (x>=0 && y>=0 && x<XRES && y<YRES)
4789 			{
4790 				if (elements[t].Properties & TYPE_ENERGY)
4791 					photons[y][x] = PMAP(i, t);
4792 				else
4793 				{
4794 					// Particles are sometimes allowed to go inside INVS and FILT
4795 					// To make particles collide correctly when inside these elements, these elements must not overwrite an existing pmap entry from particles inside them
4796 					if (!pmap[y][x] || (t!=PT_INVIS && t!= PT_FILT))
4797 						pmap[y][x] = PMAP(i, t);
4798 					// (there are a few exceptions, including energy particles - currently no limit on stacking those)
4799 					if (t!=PT_THDR && t!=PT_EMBR && t!=PT_FIGH && t!=PT_PLSM)
4800 						pmap_count[y][x]++;
4801 				}
4802 				inBounds = true;
4803 			}
4804 			lastPartUsed = i;
4805 			NUM_PARTS ++;
4806 
4807 			//decrease particle life
4808 			if (do_life_dec && (!sys_pause || framerender))
4809 			{
4810 				if (t<0 || t>=PT_NUM || !elements[t].Enabled)
4811 				{
4812 					kill_part(i);
4813 					continue;
4814 				}
4815 
4816 				if (elementRecount)
4817 					elementCount[t]++;
4818 
4819 				unsigned int elem_properties = elements[t].Properties;
4820 				if (parts[i].life>0 && (elem_properties&PROP_LIFE_DEC) && !(inBounds && bmap[y/CELL][x/CELL] == WL_STASIS && emap[y/CELL][x/CELL]<8))
4821 				{
4822 					// automatically decrease life
4823 					parts[i].life--;
4824 					if (parts[i].life<=0 && (elem_properties&(PROP_LIFE_KILL_DEC|PROP_LIFE_KILL)))
4825 					{
4826 						// kill on change to no life
4827 						kill_part(i);
4828 						continue;
4829 					}
4830 				}
4831 				else if (parts[i].life<=0 && (elem_properties&PROP_LIFE_KILL) && !(inBounds && bmap[y/CELL][x/CELL] == WL_STASIS && emap[y/CELL][x/CELL]<8))
4832 				{
4833 					// kill if no life
4834 					kill_part(i);
4835 					continue;
4836 				}
4837 			}
4838 		}
4839 		else
4840 		{
4841 			if (lastPartUnused<0) pfree = i;
4842 			else parts[lastPartUnused].life = i;
4843 			lastPartUnused = i;
4844 		}
4845 	}
4846 	if (lastPartUnused == -1)
4847 	{
4848 		if (parts_lastActiveIndex>=NPART-1)
4849 			pfree = -1;
4850 		else
4851 			pfree = parts_lastActiveIndex+1;
4852 	}
4853 	else
4854 	{
4855 		if (parts_lastActiveIndex>=NPART-1)
4856 			parts[lastPartUnused].life = -1;
4857 		else
4858 			parts[lastPartUnused].life = parts_lastActiveIndex+1;
4859 	}
4860 	parts_lastActiveIndex = lastPartUsed;
4861 	if (elementRecount && (!sys_pause || framerender))
4862 		elementRecount = false;
4863 }
4864 
CheckStacking()4865 void Simulation::CheckStacking()
4866 {
4867 	bool excessive_stacking_found = false;
4868 	force_stacking_check = false;
4869 	for (int y = 0; y < YRES; y++)
4870 	{
4871 		for (int x = 0; x < XRES; x++)
4872 		{
4873 			// Use a threshold, since some particle stacking can be normal (e.g. BIZR + FILT)
4874 			// Setting pmap_count[y][x] > NPART means BHOL will form in that spot
4875 			if (pmap_count[y][x]>5)
4876 			{
4877 				if (bmap[y/CELL][x/CELL]==WL_EHOLE)
4878 				{
4879 					// Allow more stacking in E-hole
4880 					if (pmap_count[y][x]>1500)
4881 					{
4882 						pmap_count[y][x] = pmap_count[y][x] + NPART;
4883 						excessive_stacking_found = 1;
4884 					}
4885 				}
4886 				else if (pmap_count[y][x]>1500 || (unsigned int)RNG::Ref().between(0, 1599) <= (pmap_count[y][x]+100))
4887 				{
4888 					pmap_count[y][x] = pmap_count[y][x] + NPART;
4889 					excessive_stacking_found = true;
4890 				}
4891 			}
4892 		}
4893 	}
4894 	if (excessive_stacking_found)
4895 	{
4896 		for (int i = 0; i <= parts_lastActiveIndex; i++)
4897 		{
4898 			if (parts[i].type)
4899 			{
4900 				int t = parts[i].type;
4901 				int x = (int)(parts[i].x+0.5f);
4902 				int y = (int)(parts[i].y+0.5f);
4903 				if (x>=0 && y>=0 && x<XRES && y<YRES && !(elements[t].Properties&TYPE_ENERGY))
4904 				{
4905 					if (pmap_count[y][x]>=NPART)
4906 					{
4907 						if (pmap_count[y][x]>NPART)
4908 						{
4909 							create_part(i, x, y, PT_NBHL);
4910 							parts[i].temp = MAX_TEMP;
4911 							parts[i].tmp = pmap_count[y][x]-NPART;//strength of grav field
4912 							if (parts[i].tmp>51200) parts[i].tmp = 51200;
4913 							pmap_count[y][x] = NPART;
4914 						}
4915 						else
4916 						{
4917 							kill_part(i);
4918 						}
4919 					}
4920 				}
4921 			}
4922 		}
4923 	}
4924 }
4925 
4926 //updates pmap, gol, and some other simulation stuff (but not particles)
BeforeSim()4927 void Simulation::BeforeSim()
4928 {
4929 	if (!sys_pause||framerender)
4930 	{
4931 		air->update_air();
4932 
4933 		if(aheat_enable)
4934 			air->update_airh();
4935 
4936 		if(grav->IsEnabled())
4937 		{
4938 			grav->gravity_update_async();
4939 
4940 			//Get updated buffer pointers for gravity
4941 			gravx = grav->gravx;
4942 			gravy = grav->gravy;
4943 			gravp = grav->gravp;
4944 			gravmap = grav->gravmap;
4945 		}
4946 		if(emp_decor>0)
4947 			emp_decor -= emp_decor/25+2;
4948 		if(emp_decor < 0)
4949 			emp_decor = 0;
4950 		etrd_count_valid = false;
4951 		etrd_life0_count = 0;
4952 
4953 		currentTick++;
4954 
4955 		elementRecount |= !(currentTick%180);
4956 		if (elementRecount)
4957 			std::fill(elementCount, elementCount+PT_NUM, 0);
4958 	}
4959 	sandcolour = (int)(20.0f*sin((float)sandcolour_frame*(M_PI/180.0f)));
4960 	sandcolour_frame = (sandcolour_frame+1)%360;
4961 
4962 	if (gravWallChanged)
4963 	{
4964 		grav->gravity_mask();
4965 		gravWallChanged = false;
4966 	}
4967 
4968 	if (debug_currentParticle == 0)
4969 		RecalcFreeParticles(true);
4970 
4971 	if (!sys_pause || framerender)
4972 	{
4973 		// decrease wall conduction, make walls block air and ambient heat
4974 		int x, y;
4975 		for (y = 0; y < YRES/CELL; y++)
4976 		{
4977 			for (x = 0; x < XRES/CELL; x++)
4978 			{
4979 				if (emap[y][x])
4980 					emap[y][x] --;
4981 				air->bmap_blockair[y][x] = (bmap[y][x]==WL_WALL || bmap[y][x]==WL_WALLELEC || bmap[y][x]==WL_BLOCKAIR || (bmap[y][x]==WL_EWALL && !emap[y][x]));
4982 				air->bmap_blockairh[y][x] = (bmap[y][x]==WL_WALL || bmap[y][x]==WL_WALLELEC || bmap[y][x]==WL_BLOCKAIR || bmap[y][x]==WL_GRAV || (bmap[y][x]==WL_EWALL && !emap[y][x])) ? 0x8:0;
4983 			}
4984 		}
4985 
4986 		// check for stacking and create BHOL if found
4987 		if (force_stacking_check || RNG::Ref().chance(1, 10))
4988 		{
4989 			CheckStacking();
4990 		}
4991 
4992 		// LOVE and LOLZ element handling
4993 		if (elementCount[PT_LOVE] > 0 || elementCount[PT_LOLZ] > 0)
4994 		{
4995 			int nx, nnx, ny, nny, r, rt;
4996 			for (ny=0; ny<YRES-4; ny++)
4997 			{
4998 				for (nx=0; nx<XRES-4; nx++)
4999 				{
5000 					r=pmap[ny][nx];
5001 					if (!r)
5002 					{
5003 						continue;
5004 					}
5005 					else if ((ny<9||nx<9||ny>YRES-7||nx>XRES-10)&&(parts[ID(r)].type==PT_LOVE||parts[ID(r)].type==PT_LOLZ))
5006 						kill_part(ID(r));
5007 					else if (parts[ID(r)].type==PT_LOVE)
5008 					{
5009 						Element_LOVE_love[nx/9][ny/9] = 1;
5010 					}
5011 					else if (parts[ID(r)].type==PT_LOLZ)
5012 					{
5013 						Element_LOLZ_lolz[nx/9][ny/9] = 1;
5014 					}
5015 				}
5016 			}
5017 			for (nx=9; nx<=XRES-18; nx++)
5018 			{
5019 				for (ny=9; ny<=YRES-7; ny++)
5020 				{
5021 					if (Element_LOVE_love[nx/9][ny/9]==1)
5022 					{
5023 						for ( nnx=0; nnx<9; nnx++)
5024 							for ( nny=0; nny<9; nny++)
5025 							{
5026 								if (ny+nny>0&&ny+nny<YRES&&nx+nnx>=0&&nx+nnx<XRES)
5027 								{
5028 									rt=pmap[ny+nny][nx+nnx];
5029 									if (!rt&&Element_LOVE_RuleTable[nnx][nny]==1)
5030 										create_part(-1,nx+nnx,ny+nny,PT_LOVE);
5031 									else if (!rt)
5032 										continue;
5033 									else if (parts[ID(rt)].type==PT_LOVE&&Element_LOVE_RuleTable[nnx][nny]==0)
5034 										kill_part(ID(rt));
5035 								}
5036 							}
5037 					}
5038 					Element_LOVE_love[nx/9][ny/9]=0;
5039 					if (Element_LOLZ_lolz[nx/9][ny/9]==1)
5040 					{
5041 						for ( nnx=0; nnx<9; nnx++)
5042 							for ( nny=0; nny<9; nny++)
5043 							{
5044 								if (ny+nny>0&&ny+nny<YRES&&nx+nnx>=0&&nx+nnx<XRES)
5045 								{
5046 									rt=pmap[ny+nny][nx+nnx];
5047 									if (!rt&&Element_LOLZ_RuleTable[nny][nnx]==1)
5048 										create_part(-1,nx+nnx,ny+nny,PT_LOLZ);
5049 									else if (!rt)
5050 										continue;
5051 									else if (parts[ID(rt)].type==PT_LOLZ&&Element_LOLZ_RuleTable[nny][nnx]==0)
5052 										kill_part(ID(rt));
5053 
5054 								}
5055 							}
5056 					}
5057 					Element_LOLZ_lolz[nx/9][ny/9]=0;
5058 				}
5059 			}
5060 		}
5061 
5062 		// make WIRE work
5063 		if(elementCount[PT_WIRE] > 0)
5064 		{
5065 			for (int nx = 0; nx < XRES; nx++)
5066 			{
5067 				for (int ny = 0; ny < YRES; ny++)
5068 				{
5069 					int r = pmap[ny][nx];
5070 					if (!r)
5071 						continue;
5072 					if(parts[ID(r)].type == PT_WIRE)
5073 						parts[ID(r)].tmp = parts[ID(r)].ctype;
5074 				}
5075 			}
5076 		}
5077 
5078 		// update PPIP tmp?
5079 		if (Element_PPIP_ppip_changed)
5080 		{
5081 			for (int i = 0; i <= parts_lastActiveIndex; i++)
5082 			{
5083 				if (parts[i].type==PT_PPIP)
5084 				{
5085 					parts[i].tmp |= (parts[i].tmp&0xE0000000)>>3;
5086 					parts[i].tmp &= ~0xE0000000;
5087 				}
5088 			}
5089 			Element_PPIP_ppip_changed = 0;
5090 		}
5091 
5092 		// Simulate GoL
5093 		// GSPEED is frames per generation
5094 		if (elementCount[PT_LIFE]>0 && ++CGOL>=GSPEED)
5095 		{
5096 			SimulateGoL();
5097 		}
5098 
5099 		// wifi channel reseting
5100 		if (ISWIRE > 0)
5101 		{
5102 			for (int q = 0; q < (int)(MAX_TEMP-73.15f)/100+2; q++)
5103 			{
5104 				wireless[q][0] = wireless[q][1];
5105 				wireless[q][1] = 0;
5106 			}
5107 			ISWIRE--;
5108 		}
5109 
5110 		// spawn STKM and STK2
5111 		if (!player.spwn && player.spawnID >= 0)
5112 			create_part(-1, (int)parts[player.spawnID].x, (int)parts[player.spawnID].y, PT_STKM);
5113 		if (!player2.spwn && player2.spawnID >= 0)
5114 			create_part(-1, (int)parts[player2.spawnID].x, (int)parts[player2.spawnID].y, PT_STKM2);
5115 
5116 		// particle update happens right after this function (called separately)
5117 	}
5118 }
5119 
AfterSim()5120 void Simulation::AfterSim()
5121 {
5122 	if (emp_trigger_count)
5123 	{
5124 		// pitiful attempt at trying to keep code relating to a given element in the same file
5125 		void Element_EMP_Trigger(Simulation *sim, int triggerCount);
5126 		Element_EMP_Trigger(this, emp_trigger_count);
5127 		emp_trigger_count = 0;
5128 	}
5129 }
5130 
~Simulation()5131 Simulation::~Simulation()
5132 {
5133 	delete grav;
5134 	delete air;
5135 }
5136 
Simulation()5137 Simulation::Simulation():
5138 	replaceModeSelected(0),
5139 	replaceModeFlags(0),
5140 	debug_currentParticle(0),
5141 	ISWIRE(0),
5142 	force_stacking_check(false),
5143 	emp_decor(0),
5144 	emp_trigger_count(0),
5145 	etrd_count_valid(false),
5146 	etrd_life0_count(0),
5147 	lightningRecreate(0),
5148 	gravWallChanged(false),
5149 	CGOL(0),
5150 	GSPEED(1),
5151 	edgeMode(0),
5152 	gravityMode(0),
5153 	legacy_enable(0),
5154 	aheat_enable(0),
5155 	water_equal_test(0),
5156 	sys_pause(0),
5157 	framerender(0),
5158 	pretty_powder(0),
5159 	sandcolour_frame(0),
5160 	deco_space(0)
5161 {
5162 	int tportal_rx[] = {-1, 0, 1, 1, 1, 0,-1,-1};
5163 	int tportal_ry[] = {-1,-1,-1, 0, 1, 1, 1, 0};
5164 
5165 	memcpy(portal_rx, tportal_rx, sizeof(tportal_rx));
5166 	memcpy(portal_ry, tportal_ry, sizeof(tportal_ry));
5167 
5168 	currentTick = 0;
5169 	std::fill(elementCount, elementCount+PT_NUM, 0);
5170 	elementRecount = true;
5171 
5172 	//Create and attach gravity simulation
5173 	grav = new Gravity();
5174 	//Give air sim references to our data
5175 	grav->bmap = bmap;
5176 	//Gravity sim gives us maps to use
5177 	gravx = grav->gravx;
5178 	gravy = grav->gravy;
5179 	gravp = grav->gravp;
5180 	gravmap = grav->gravmap;
5181 
5182 	//Create and attach air simulation
5183 	air = new Air(*this);
5184 	//Give air sim references to our data
5185 	air->bmap = bmap;
5186 	air->emap = emap;
5187 	air->fvx = fvx;
5188 	air->fvy = fvy;
5189 	//Air sim gives us maps to use
5190 	vx = air->vx;
5191 	vy = air->vy;
5192 	pv = air->pv;
5193 	hv = air->hv;
5194 
5195 	msections = LoadMenus();
5196 	wtypes = LoadWalls();
5197 	platent = LoadLatent();
5198 	std::copy(GetElements().begin(), GetElements().end(), elements.begin());
5199 	tools = GetTools();
5200 	grule = LoadGOLRules();
5201 	gmenu = LoadGOLMenu();
5202 
5203 	player.comm = 0;
5204 	player2.comm = 0;
5205 
5206 	init_can_move();
5207 	clear_sim();
5208 
5209 	grav->gravity_mask();
5210 }
5211 
ElementResolve(int type,int ctype)5212 String Simulation::ElementResolve(int type, int ctype)
5213 {
5214 	if (type == PT_LIFE && ctype >= 0 && ctype < NGOL)
5215 	{
5216 		return gmenu[ctype].name;
5217 	}
5218 	else if (type >= 0 && type < PT_NUM)
5219 	{
5220 		return elements[type].Name;
5221 	}
5222 	return "Empty";
5223 }
5224 
BasicParticleInfo(Particle const & sample_part)5225 String Simulation::BasicParticleInfo(Particle const &sample_part)
5226 {
5227 	StringBuilder sampleInfo;
5228 	int type = sample_part.type;
5229 	int ctype = sample_part.ctype;
5230 	int pavg1int = (int)sample_part.pavg[1];
5231 	if (type == PT_LAVA && ctype && IsValidElement(ctype))
5232 	{
5233 		sampleInfo << "Molten " << ElementResolve(ctype, -1);
5234 	}
5235 	else if ((type == PT_PIPE || type == PT_PPIP) && ctype && IsValidElement(ctype))
5236 	{
5237 		if (ctype == PT_LAVA && pavg1int && IsValidElement(pavg1int))
5238 		{
5239 			sampleInfo << ElementResolve(type, -1) << " with molten " << ElementResolve(pavg1int, -1);
5240 		}
5241 		else
5242 		{
5243 			sampleInfo << ElementResolve(type, -1) << " with " << ElementResolve(ctype, pavg1int);
5244 		}
5245 	}
5246 	else
5247 	{
5248 		sampleInfo << ElementResolve(type, ctype);
5249 	}
5250 	return sampleInfo.Build();
5251 }
5252 
InBounds(int x,int y)5253 bool Simulation::InBounds(int x, int y)
5254 {
5255 	return (x>=0 && y>=0 && x<XRES && y<YRES);
5256 }
5257 
remainder_p(int x,int y)5258 int Simulation::remainder_p(int x, int y)
5259 {
5260 	return (x % y) + (x>=0 ? 0 : y);
5261 }
5262 
remainder_p(float x,float y)5263 float Simulation::remainder_p(float x, float y)
5264 {
5265 	return std::fmod(x, y) + (x>=0 ? 0 : y);
5266 }
5267 
ce_log2(size_t n)5268 constexpr size_t ce_log2(size_t n)
5269 {
5270 	return ((n < 2) ? 1 : 1 + ce_log2(n / 2));
5271 }
5272 static_assert(PMAPBITS <= 16, "PMAPBITS is too large");
5273 // * This will technically fail in some cases where (XRES * YRES) << PMAPBITS would
5274 //   fit in 31 bits but multiplication is evil and wraps around without you knowing it.
5275 // * Whoever runs into a problem with this (e.g. with XRES = 612, YRES = 384 and
5276 //   PMAPBITS = 13) should just remove the check and take responsibility otherwise.
5277 static_assert(ce_log2(XRES) + ce_log2(YRES) + PMAPBITS <= 31, "not enough space in pmap");
5278