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