#include "Simulation.h" #include #include #include #ifdef _MSC_VER #include #else #include #endif #include "Air.h" #include "Config.h" #include "CoordStack.h" #include "ElementClasses.h" #include "Gravity.h" #include "Sample.h" #include "Snapshot.h" #include "Misc.h" #include "ToolClasses.h" #include "Config.h" #include "SimulationData.h" #include "GOLString.h" #include "graphics/Renderer.h" #include "client/GameSave.h" #include "common/tpt-compat.h" #include "common/tpt-minmax.h" #include "common/tpt-rand.h" #include "gui/game/Brush.h" #ifdef LUACONSOLE #include "lua/LuaScriptInterface.h" #include "lua/LuaScriptHelper.h" #endif extern int Element_PPIP_ppip_changed; extern int Element_LOLZ_RuleTable[9][9]; extern int Element_LOLZ_lolz[XRES/9][YRES/9]; extern int Element_LOVE_RuleTable[9][9]; extern int Element_LOVE_love[XRES/9][YRES/9]; int Simulation::Load(GameSave * save, bool includePressure) { return Load(save, includePressure, 0, 0); } int Simulation::Load(GameSave * save, bool includePressure, int fullX, int fullY) { int x, y, r; if (!save) return 1; try { save->Expand(); } catch (ParseException &) { return 1; } //Align to blockMap int blockX = (fullX + CELL/2)/CELL; int blockY = (fullY + CELL/2)/CELL; fullX = blockX*CELL; fullY = blockY*CELL; unsigned int pmapmask = (1<pmapbits)-1; int partMap[PT_NUM]; for(int i = 0; i < PT_NUM; i++) { partMap[i] = i; } if(save->palette.size()) { for(std::vector::iterator iter = save->palette.begin(), end = save->palette.end(); iter != end; ++iter) { GameSave::PaletteItem pi = *iter; if (pi.second > 0 && pi.second < PT_NUM) { int myId = 0; for (int i = 0; i < PT_NUM; i++) { if (elements[i].Enabled && elements[i].Identifier == pi.first) myId = i; } // if this is a custom element, set the ID to the ID we found when comparing identifiers in the palette map // set type to 0 if we couldn't find an element with that identifier present when loading, // 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 if (myId != 0 || !pi.first.BeginsWith("DEFAULT_PT_")) partMap[pi.second] = myId; } } } int i; // Map of soap particles loaded into this save, old ID -> new ID std::map soapList; for (int n = 0; n < NPART && n < save->particlesCount; n++) { Particle tempPart = save->particles[n]; tempPart.x += (float)fullX; tempPart.y += (float)fullY; x = int(tempPart.x + 0.5f); y = int(tempPart.y + 0.5f); if (tempPart.type >= 0 && tempPart.type < PT_NUM) tempPart.type = partMap[tempPart.type]; else continue; // Ensure we can spawn this element if ((player.spwn == 1 && tempPart.type==PT_STKM) || (player2.spwn == 1 && tempPart.type==PT_STKM2)) continue; if ((tempPart.type == PT_SPAWN && elementCount[PT_SPAWN]) || (tempPart.type == PT_SPAWN2 && elementCount[PT_SPAWN2])) continue; bool Element_FIGH_CanAlloc(Simulation *sim); if (tempPart.type == PT_FIGH && !Element_FIGH_CanAlloc(this)) continue; if (!elements[tempPart.type].Enabled) continue; // These store type in ctype, but are special because they store extra information in the bits after type if (tempPart.type == PT_CRAY || tempPart.type == PT_DRAY || tempPart.type == PT_CONV) { int ctype = tempPart.ctype & pmapmask; int extra = tempPart.ctype >> save->pmapbits; if (ctype >= 0 && ctype < PT_NUM) ctype = partMap[ctype]; tempPart.ctype = PMAP(extra, ctype); } else if (GameSave::TypeInCtype(tempPart.type, tempPart.ctype)) { tempPart.ctype = partMap[tempPart.ctype]; } // also stores extra bits past type (only STOR right now) if (GameSave::TypeInTmp(tempPart.type)) { int tmp = tempPart.tmp & pmapmask; int extra = tempPart.tmp >> save->pmapbits; tmp = partMap[TYP(tmp)]; tempPart.tmp = PMAP(extra, tmp); } if (GameSave::TypeInTmp2(tempPart.type, tempPart.tmp2)) { tempPart.tmp2 = partMap[tempPart.tmp2]; } //Replace existing if ((r = pmap[y][x])) { elementCount[parts[ID(r)].type]--; parts[ID(r)] = tempPart; i = ID(r); elementCount[tempPart.type]++; // * pmap will be repopulated a bit later by RecalcFreeParticles; // until then, remove the entry so the next new particle at the // same position doesn't overwrite the one we've just copied pmap[y][x] = 0; } else if ((r = photons[y][x])) { elementCount[parts[ID(r)].type]--; parts[ID(r)] = tempPart; i = ID(r); elementCount[tempPart.type]++; // * same as with pmap above photons[y][x] = 0; } //Allocate new particle else { if (pfree == -1) break; i = pfree; pfree = parts[i].life; if (i > parts_lastActiveIndex) parts_lastActiveIndex = i; parts[i] = tempPart; elementCount[tempPart.type]++; } void Element_STKM_init_legs(Simulation * sim, playerst *playerp, int i); switch (parts[i].type) { case PT_STKM: Element_STKM_init_legs(this, &player, i); player.spwn = 1; player.elem = PT_DUST; if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) || (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) { player.fan = true; } if (save->stkm.rocketBoots1) player.rocketBoots = true; if (save->stkm.fan1) player.fan = true; break; case PT_STKM2: Element_STKM_init_legs(this, &player2, i); player2.spwn = 1; player2.elem = PT_DUST; if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) || (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) { player2.fan = true; } if (save->stkm.rocketBoots2) player2.rocketBoots = true; if (save->stkm.fan2) player2.fan = true; break; case PT_SPAWN: player.spawnID = i; break; case PT_SPAWN2: player2.spawnID = i; break; case PT_FIGH: { unsigned int oldTmp = parts[i].tmp; int Element_FIGH_Alloc(Simulation *sim); parts[i].tmp = Element_FIGH_Alloc(this); if (parts[i].tmp >= 0) { bool fan = false; if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) || (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) { fan = true; parts[i].ctype = 0; } fighters[parts[i].tmp].elem = PT_DUST; void Element_FIGH_NewFighter(Simulation *sim, int fighterID, int i, int elem); Element_FIGH_NewFighter(this, parts[i].tmp, i, parts[i].ctype); if (fan) fighters[parts[i].tmp].fan = true; for (unsigned int fighNum : save->stkm.rocketBootsFigh) { if (fighNum == oldTmp) fighters[parts[i].tmp].rocketBoots = true; } for (unsigned int fighNum : save->stkm.fanFigh) { if (fighNum == oldTmp) fighters[parts[i].tmp].fan = true; } } else { // Should not be possible because we verify with CanAlloc above this parts[i].type = 0; } break; } case PT_SOAP: soapList.insert(std::pair(n, i)); break; // List of elements that load pavg with a multiplicative bias of 2**6 // (or not at all if pressure is not loaded). // If you change this list, change it in GameSave::serialiseOPS and GameSave::readOPS too! case PT_QRTZ: case PT_GLAS: case PT_TUNG: if (!includePressure) { parts[i].pavg[0] = 0; parts[i].pavg[1] = 0; } break; } } parts_lastActiveIndex = NPART-1; force_stacking_check = true; Element_PPIP_ppip_changed = 1; RecalcFreeParticles(false); // fix SOAP links using soapList, a map of old particle ID -> new particle ID // loop through every old particle (loaded from save), and convert .tmp / .tmp2 for (std::map::iterator iter = soapList.begin(), end = soapList.end(); iter != end; ++iter) { int i = (*iter).second; if ((parts[i].ctype & 0x2) == 2) { std::map::iterator n = soapList.find(parts[i].tmp); if (n != end) parts[i].tmp = n->second; // sometimes the proper SOAP isn't found. It should remove the link, but seems to break some saves // so just ignore it } if ((parts[i].ctype & 0x4) == 4) { std::map::iterator n = soapList.find(parts[i].tmp2); if (n != end) parts[i].tmp2 = n->second; // sometimes the proper SOAP isn't found. It should remove the link, but seems to break some saves // so just ignore it } } for (size_t i = 0; i < save->signs.size() && signs.size() < MAXSIGNS; i++) { if (save->signs[i].text[0]) { sign tempSign = save->signs[i]; tempSign.x += fullX; tempSign.y += fullY; signs.push_back(tempSign); } } for(int saveBlockX = 0; saveBlockX < save->blockWidth; saveBlockX++) { for(int saveBlockY = 0; saveBlockY < save->blockHeight; saveBlockY++) { if(save->blockMap[saveBlockY][saveBlockX]) { bmap[saveBlockY+blockY][saveBlockX+blockX] = save->blockMap[saveBlockY][saveBlockX]; fvx[saveBlockY+blockY][saveBlockX+blockX] = save->fanVelX[saveBlockY][saveBlockX]; fvy[saveBlockY+blockY][saveBlockX+blockX] = save->fanVelY[saveBlockY][saveBlockX]; } if (includePressure) { if (save->hasPressure) { pv[saveBlockY+blockY][saveBlockX+blockX] = save->pressure[saveBlockY][saveBlockX]; vx[saveBlockY+blockY][saveBlockX+blockX] = save->velocityX[saveBlockY][saveBlockX]; vy[saveBlockY+blockY][saveBlockX+blockX] = save->velocityY[saveBlockY][saveBlockX]; } if (save->hasAmbientHeat) hv[saveBlockY+blockY][saveBlockX+blockX] = save->ambientHeat[saveBlockY][saveBlockX]; } } } gravWallChanged = true; air->RecalculateBlockAirMaps(); return 0; } GameSave * Simulation::Save(bool includePressure) { return Save(includePressure, 0, 0, XRES-1, YRES-1); } GameSave * Simulation::Save(bool includePressure, int fullX, int fullY, int fullX2, int fullY2) { int blockX, blockY, blockX2, blockY2, blockW, blockH; //Normalise incoming coords int swapTemp; if(fullY>fullY2) { swapTemp = fullY; fullY = fullY2; fullY2 = swapTemp; } if(fullX>fullX2) { swapTemp = fullX; fullX = fullX2; fullX2 = swapTemp; } //Align coords to blockMap blockX = fullX/CELL; blockY = fullY/CELL; blockX2 = (fullX2+CELL)/CELL; blockY2 = (fullY2+CELL)/CELL; blockW = blockX2-blockX; blockH = blockY2-blockY; GameSave * newSave = new GameSave(blockW, blockH); int storedParts = 0; int elementCount[PT_NUM]; std::fill(elementCount, elementCount+PT_NUM, 0); // Map of soap particles loaded into this save, old ID -> new ID // Now stores all particles, not just SOAP (but still only used for soap) std::map particleMap; std::set paletteSet; for (int i = 0; i < NPART; i++) { int x, y; x = int(parts[i].x + 0.5f); y = int(parts[i].y + 0.5f); if (parts[i].type && x >= fullX && y >= fullY && x <= fullX2 && y <= fullY2) { Particle tempPart = parts[i]; tempPart.x -= blockX*CELL; tempPart.y -= blockY*CELL; if (elements[tempPart.type].Enabled) { particleMap.insert(std::pair(i, storedParts)); *newSave << tempPart; storedParts++; elementCount[tempPart.type]++; paletteSet.insert(tempPart.type); if (GameSave::TypeInCtype(tempPart.type, tempPart.ctype)) paletteSet.insert(tempPart.ctype); if (GameSave::TypeInTmp(tempPart.type)) paletteSet.insert(TYP(tempPart.tmp)); if (GameSave::TypeInTmp2(tempPart.type, tempPart.tmp2)) paletteSet.insert(tempPart.tmp2); } } } for (int ID : paletteSet) newSave->palette.push_back(GameSave::PaletteItem(elements[ID].Identifier, ID)); if (storedParts && elementCount[PT_SOAP]) { // fix SOAP links using particleMap, a map of old particle ID -> new particle ID // loop through every new particle (saved into the save), and convert .tmp / .tmp2 for (std::map::iterator iter = particleMap.begin(), end = particleMap.end(); iter != end; ++iter) { int i = (*iter).second; if (newSave->particles[i].type != PT_SOAP) continue; if ((newSave->particles[i].ctype & 0x2) == 2) { std::map::iterator n = particleMap.find(newSave->particles[i].tmp); if (n != end) newSave->particles[i].tmp = n->second; else { newSave->particles[i].tmp = 0; newSave->particles[i].ctype ^= 2; } } if ((newSave->particles[i].ctype & 0x4) == 4) { std::map::iterator n = particleMap.find(newSave->particles[i].tmp2); if (n != end) newSave->particles[i].tmp2 = n->second; else { newSave->particles[i].tmp2 = 0; newSave->particles[i].ctype ^= 4; } } } } for (size_t i = 0; i < MAXSIGNS && i < signs.size(); i++) { if(signs[i].text.length() && signs[i].x >= fullX && signs[i].y >= fullY && signs[i].x <= fullX2 && signs[i].y <= fullY2) { sign tempSign = signs[i]; tempSign.x -= blockX*CELL; tempSign.y -= blockY*CELL; *newSave << tempSign; } } for(int saveBlockX = 0; saveBlockX < newSave->blockWidth; saveBlockX++) { for(int saveBlockY = 0; saveBlockY < newSave->blockHeight; saveBlockY++) { if(bmap[saveBlockY+blockY][saveBlockX+blockX]) { newSave->blockMap[saveBlockY][saveBlockX] = bmap[saveBlockY+blockY][saveBlockX+blockX]; newSave->fanVelX[saveBlockY][saveBlockX] = fvx[saveBlockY+blockY][saveBlockX+blockX]; newSave->fanVelY[saveBlockY][saveBlockX] = fvy[saveBlockY+blockY][saveBlockX+blockX]; } if (includePressure) { newSave->pressure[saveBlockY][saveBlockX] = pv[saveBlockY+blockY][saveBlockX+blockX]; newSave->velocityX[saveBlockY][saveBlockX] = vx[saveBlockY+blockY][saveBlockX+blockX]; newSave->velocityY[saveBlockY][saveBlockX] = vy[saveBlockY+blockY][saveBlockX+blockX]; newSave->ambientHeat[saveBlockY][saveBlockX] = hv[saveBlockY+blockY][saveBlockX+blockX]; } } } if (includePressure) { newSave->hasPressure = true; newSave->hasAmbientHeat = true; } newSave->stkm.rocketBoots1 = player.rocketBoots; newSave->stkm.rocketBoots2 = player2.rocketBoots; newSave->stkm.fan1 = player.fan; newSave->stkm.fan2 = player2.fan; for (unsigned char i = 0; i < MAX_FIGHTERS; i++) { if (fighters[i].rocketBoots) newSave->stkm.rocketBootsFigh.push_back(i); if (fighters[i].fan) newSave->stkm.fanFigh.push_back(i); } SaveSimOptions(newSave); newSave->pmapbits = PMAPBITS; return newSave; } void Simulation::SaveSimOptions(GameSave * gameSave) { if (!gameSave) return; gameSave->gravityMode = gravityMode; gameSave->airMode = air->airMode; gameSave->edgeMode = edgeMode; gameSave->legacyEnable = legacy_enable; gameSave->waterEEnabled = water_equal_test; gameSave->gravityEnable = grav->IsEnabled(); gameSave->aheatEnable = aheat_enable; } Snapshot * Simulation::CreateSnapshot() { Snapshot * snap = new Snapshot(); snap->AirPressure.insert(snap->AirPressure.begin(), &pv[0][0], &pv[0][0]+((XRES/CELL)*(YRES/CELL))); snap->AirVelocityX.insert(snap->AirVelocityX.begin(), &vx[0][0], &vx[0][0]+((XRES/CELL)*(YRES/CELL))); snap->AirVelocityY.insert(snap->AirVelocityY.begin(), &vy[0][0], &vy[0][0]+((XRES/CELL)*(YRES/CELL))); snap->AmbientHeat.insert(snap->AmbientHeat.begin(), &hv[0][0], &hv[0][0]+((XRES/CELL)*(YRES/CELL))); snap->Particles.insert(snap->Particles.begin(), parts, parts+parts_lastActiveIndex+1); snap->PortalParticles.insert(snap->PortalParticles.begin(), &portalp[0][0][0], &portalp[CHANNELS-1][8-1][80-1]); snap->WirelessData.insert(snap->WirelessData.begin(), &wireless[0][0], &wireless[CHANNELS-1][2-1]); snap->GravVelocityX.insert(snap->GravVelocityX.begin(), gravx, gravx+((XRES/CELL)*(YRES/CELL))); snap->GravVelocityY.insert(snap->GravVelocityY.begin(), gravy, gravy+((XRES/CELL)*(YRES/CELL))); snap->GravValue.insert(snap->GravValue.begin(), gravp, gravp+((XRES/CELL)*(YRES/CELL))); snap->GravMap.insert(snap->GravMap.begin(), gravmap, gravmap+((XRES/CELL)*(YRES/CELL))); snap->BlockMap.insert(snap->BlockMap.begin(), &bmap[0][0], &bmap[0][0]+((XRES/CELL)*(YRES/CELL))); snap->ElecMap.insert(snap->ElecMap.begin(), &emap[0][0], &emap[0][0]+((XRES/CELL)*(YRES/CELL))); snap->FanVelocityX.insert(snap->FanVelocityX.begin(), &fvx[0][0], &fvx[0][0]+((XRES/CELL)*(YRES/CELL))); snap->FanVelocityY.insert(snap->FanVelocityY.begin(), &fvy[0][0], &fvy[0][0]+((XRES/CELL)*(YRES/CELL))); snap->stickmen.push_back(player2); snap->stickmen.push_back(player); snap->stickmen.insert(snap->stickmen.begin(), &fighters[0], &fighters[MAX_FIGHTERS]); snap->signs = signs; return snap; } void Simulation::Restore(const Snapshot & snap) { parts_lastActiveIndex = NPART-1; elementRecount = true; force_stacking_check = true; std::copy(snap.AirPressure.begin(), snap.AirPressure.end(), &pv[0][0]); std::copy(snap.AirVelocityX.begin(), snap.AirVelocityX.end(), &vx[0][0]); std::copy(snap.AirVelocityY.begin(), snap.AirVelocityY.end(), &vy[0][0]); std::copy(snap.AmbientHeat.begin(), snap.AmbientHeat.end(), &hv[0][0]); for (int i = 0; i < NPART; i++) parts[i].type = 0; std::copy(snap.Particles.begin(), snap.Particles.end(), parts); parts_lastActiveIndex = NPART-1; RecalcFreeParticles(false); std::copy(snap.PortalParticles.begin(), snap.PortalParticles.end(), &portalp[0][0][0]); std::copy(snap.WirelessData.begin(), snap.WirelessData.end(), &wireless[0][0]); if (grav->IsEnabled()) { grav->Clear(); std::copy(snap.GravVelocityX.begin(), snap.GravVelocityX.end(), gravx); std::copy(snap.GravVelocityY.begin(), snap.GravVelocityY.end(), gravy); std::copy(snap.GravValue.begin(), snap.GravValue.end(), gravp); std::copy(snap.GravMap.begin(), snap.GravMap.end(), gravmap); } gravWallChanged = true; std::copy(snap.BlockMap.begin(), snap.BlockMap.end(), &bmap[0][0]); std::copy(snap.ElecMap.begin(), snap.ElecMap.end(), &emap[0][0]); std::copy(snap.FanVelocityX.begin(), snap.FanVelocityX.end(), &fvx[0][0]); std::copy(snap.FanVelocityY.begin(), snap.FanVelocityY.end(), &fvy[0][0]); std::copy(snap.stickmen.begin(), snap.stickmen.end()-2, &fighters[0]); player = snap.stickmen[snap.stickmen.size()-1]; player2 = snap.stickmen[snap.stickmen.size()-2]; signs = snap.signs; } void Simulation::clear_area(int area_x, int area_y, int area_w, int area_h) { float fx = area_x-.5f, fy = area_y-.5f; for (int i = 0; i <= parts_lastActiveIndex; i++) { if (parts[i].type) if (parts[i].x >= fx && parts[i].x <= fx+area_w+1 && parts[i].y >= fy && parts[i].y <= fy+area_h+1) kill_part(i); } int cx1 = area_x/CELL, cy1 = area_y/CELL, cx2 = (area_x+area_w)/CELL, cy2 = (area_y+area_h)/CELL; for (int y = cy1; y <= cy2; y++) { for (int x = cx1; x <= cx2; x++) { if (bmap[y][x] == WL_GRAV) gravWallChanged = true; bmap[y][x] = 0; emap[y][x] = 0; } } for( int i = signs.size()-1; i >= 0; i--) { 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) { signs.erase(signs.begin()+i); } } } bool Simulation::FloodFillPmapCheck(int x, int y, int type) { if (type == 0) return !pmap[y][x] && !photons[y][x]; if (elements[type].Properties&TYPE_ENERGY) return TYP(photons[y][x]) == type; else return TYP(pmap[y][x]) == type; } CoordStack& Simulation::getCoordStackSingleton() { // Future-proofing in case Simulation is later multithreaded thread_local CoordStack cs; return cs; } int Simulation::flood_prop(int x, int y, size_t propoffset, PropertyValue propvalue, StructProperty::PropertyType proptype) { int i, x1, x2, dy = 1; int did_something = 0; int r = pmap[y][x]; if (!r) r = photons[y][x]; if (!r) return 0; int parttype = TYP(r); char * bitmap = (char*)malloc(XRES*YRES); //Bitmap for checking if (!bitmap) return -1; memset(bitmap, 0, XRES*YRES); try { CoordStack& cs = getCoordStackSingleton(); cs.clear(); cs.push(x, y); do { cs.pop(x, y); x1 = x2 = x; while (x1>=CELL) { if (!FloodFillPmapCheck(x1-1, y, parttype) || bitmap[(y*XRES)+x1-1]) break; x1--; } while (x2=CELL+dy) for (x=x1; x<=x2; x++) if (FloodFillPmapCheck(x, y-dy, parttype) && !bitmap[((y-dy)*XRES)+x]) cs.push(x, y-dy); if (y0); } catch (std::exception& e) { std::cerr << e.what() << std::endl; free(bitmap); return -1; } free(bitmap); return did_something; } SimulationSample Simulation::GetSample(int x, int y) { SimulationSample sample; sample.PositionX = x; sample.PositionY = y; if (x >= 0 && x < XRES && y >= 0 && y < YRES) { if (photons[y][x]) { sample.particle = parts[ID(photons[y][x])]; sample.ParticleID = ID(photons[y][x]); } else if (pmap[y][x]) { sample.particle = parts[ID(pmap[y][x])]; sample.ParticleID = ID(pmap[y][x]); } if (bmap[y/CELL][x/CELL]) { sample.WallType = bmap[y/CELL][x/CELL]; } sample.AirPressure = pv[y/CELL][x/CELL]; sample.AirTemperature = hv[y/CELL][x/CELL]; sample.AirVelocityX = vx[y/CELL][x/CELL]; sample.AirVelocityY = vy[y/CELL][x/CELL]; if(grav->IsEnabled()) { sample.Gravity = gravp[(y/CELL)*(XRES/CELL)+(x/CELL)]; sample.GravityVelocityX = gravx[(y/CELL)*(XRES/CELL)+(x/CELL)]; sample.GravityVelocityY = gravy[(y/CELL)*(XRES/CELL)+(x/CELL)]; } } else sample.isMouseInSim = false; sample.NumParts = NUM_PARTS; return sample; } #define PMAP_CMP_CONDUCTIVE(pmap, t) (TYP(pmap)==(t) || (TYP(pmap)==PT_SPRK && parts[ID(pmap)].ctype==(t))) int Simulation::FloodINST(int x, int y) { const int cm = PT_INST; int x1, x2; int created_something = 0; if (TYP(pmap[y][x]) != cm || parts[ID(pmap[y][x])].life != 0) return 1; CoordStack& cs = getCoordStackSingleton(); cs.clear(); cs.push(x, y); try { do { cs.pop(x, y); x1 = x2 = x; // go left as far as possible while (x1>=CELL) { if (TYP(pmap[y][x1-1])!=cm || parts[ID(pmap[y][x1-1])].life!=0) { break; } x1--; } // go right as far as possible while (x2=0) created_something = 1; } // add vertically adjacent pixels to stack // (wire crossing for INST) if (y>=CELL+1 && x1==x2 && 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) && !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)) { // travelling vertically up, skipping a horizontal line if (TYP(pmap[y-2][x1])==cm && !parts[ID(pmap[y-2][x1])].life) { cs.push(x1, y-2); } } else if (y>=CELL+1) { for (x=x1; x<=x2; x++) { if (TYP(pmap[y-1][x])==cm && !parts[ID(pmap[y-1][x])].life) { 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)) { // if at the end of a horizontal section, or if it's a T junction or not a 1px wire crossing cs.push(x, y-1); } } } } if (y0); } catch (std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return created_something; } bool Simulation::flood_water(int x, int y, int i) { int x1, x2, originalY = y; int r = pmap[y][x]; if (!r) return false; // Bitmap for checking where we've already looked auto bitmapPtr = std::unique_ptr(new char[XRES * YRES]); char *bitmap = bitmapPtr.get(); std::fill(&bitmap[0], &bitmap[XRES * YRES], 0); try { CoordStack& cs = getCoordStackSingleton(); cs.clear(); cs.push(x, y); do { cs.pop(x, y); x1 = x2 = x; while (x1 >= CELL) { if (elements[TYP(pmap[y][x1 - 1])].Falldown != 2 || bitmap[(y * XRES) + x1 - 1]) break; x1--; } while (x2 < XRES-CELL) { if (elements[TYP(pmap[y][x2 + 1])].Falldown != 2 || bitmap[(y * XRES) + x1 - 1]) break; x2++; } for (int x = x1; x <= x2; x++) { if ((y - 1) > originalY && !pmap[y - 1][x]) { // Try to move the water to a random position on this line, because there's probably a free location somewhere int randPos = RNG::Ref().between(x, x2); if (!pmap[y - 1][randPos] && eval_move(parts[i].type, randPos, y - 1, nullptr)) x = randPos; // Couldn't move to random position, so try the original position on the left else if (!eval_move(parts[i].type, x, y - 1, nullptr)) continue; int oldx = (int)(parts[i].x + 0.5f); int oldy = (int)(parts[i].y + 0.5f); pmap[y - 1][x] = pmap[oldy][oldx]; pmap[oldy][oldx] = 0; parts[i].x = x; parts[i].y = y - 1; return true; } bitmap[(y * XRES) + x] = 1; } if (y >= CELL + 1) for (int x = x1; x <= x2; x++) if (elements[TYP(pmap[y - 1][x])].Falldown == 2 && !bitmap[((y - 1) * XRES) + x]) cs.push(x, y - 1); if (y < YRES - CELL - 1) for (int x = x1; x <= x2; x++) if (elements[TYP(pmap[y + 1][x])].Falldown == 2 && !bitmap[((y + 1) * XRES) + x]) cs.push(x, y + 1); } while (cs.getSize() > 0); } catch (std::exception &e) { std::cerr << e.what() << std::endl; return false; } return false; } void Simulation::SetDecoSpace(int newDecoSpace) { switch (newDecoSpace) { case 0: // sRGB default: // anything stupid deco_space = 0; break; case 1: // linear deco_space = 1; break; case 2: // Gamma = 2.2 deco_space = 2; break; case 3: // Gamma = 1.8 deco_space = 3; break; } } void Simulation::SetEdgeMode(int newEdgeMode) { edgeMode = newEdgeMode; switch(edgeMode) { case 0: case 2: for(int i = 0; i<(XRES/CELL); i++) { bmap[0][i] = 0; bmap[YRES/CELL-1][i] = 0; } for(int i = 1; i<((YRES/CELL)-1); i++) { bmap[i][0] = 0; bmap[i][XRES/CELL-1] = 0; } break; case 1: int i; for(i=0; i<(XRES/CELL); i++) { bmap[0][i] = WL_WALL; bmap[YRES/CELL-1][i] = WL_WALL; } for(i=1; i<((YRES/CELL)-1); i++) { bmap[i][0] = WL_WALL; bmap[i][XRES/CELL-1] = WL_WALL; } break; default: SetEdgeMode(0); } } #ifndef RENDERER void Simulation::ApplyDecoration(int x, int y, int colR_, int colG_, int colB_, int colA_, int mode) { int rp; float tr, tg, tb, ta, colR = colR_, colG = colG_, colB = colB_, colA = colA_; float strength = 0.01f; rp = pmap[y][x]; if (!rp) rp = photons[y][x]; if (!rp) return; ta = (parts[ID(rp)].dcolour>>24)&0xFF; tr = (parts[ID(rp)].dcolour>>16)&0xFF; tg = (parts[ID(rp)].dcolour>>8)&0xFF; tb = (parts[ID(rp)].dcolour)&0xFF; ta /= 255.0f; tr /= 255.0f; tg /= 255.0f; tb /= 255.0f; colR /= 255.0f; colG /= 255.0f; colB /= 255.0f; colA /= 255.0f; if (mode == DECO_DRAW) { ta = colA; tr = colR; tg = colG; tb = colB; } else if (mode == DECO_CLEAR) { ta = tr = tg = tb = 0.0f; } else if (mode == DECO_ADD) { //ta += (colA*strength)*colA; tr += (colR*strength)*colA; tg += (colG*strength)*colA; tb += (colB*strength)*colA; } else if (mode == DECO_SUBTRACT) { //ta -= (colA*strength)*colA; tr -= (colR*strength)*colA; tg -= (colG*strength)*colA; tb -= (colB*strength)*colA; } else if (mode == DECO_MULTIPLY) { tr *= 1.0f+(colR*strength)*colA; tg *= 1.0f+(colG*strength)*colA; tb *= 1.0f+(colB*strength)*colA; } else if (mode == DECO_DIVIDE) { tr /= 1.0f+(colR*strength)*colA; tg /= 1.0f+(colG*strength)*colA; tb /= 1.0f+(colB*strength)*colA; } else if (mode == DECO_SMUDGE) { if (x >= CELL && x < XRES-CELL && y >= CELL && y < YRES-CELL) { float tas = 0.0f, trs = 0.0f, tgs = 0.0f, tbs = 0.0f; int rx, ry; float num = 0; for (rx=-2; rx<3; rx++) for (ry=-2; ry<3; ry++) { if (abs(rx)+abs(ry) > 2 && TYP(pmap[y+ry][x+rx]) && parts[ID(pmap[y+ry][x+rx])].dcolour) { Particle part = parts[ID(pmap[y+ry][x+rx])]; num += 1.0f; float pa = ((float)((part.dcolour>>24)&0xFF)) / 255.f; float pr = ((float)((part.dcolour>>16)&0xFF)) / 255.f; float pg = ((float)((part.dcolour>> 8)&0xFF)) / 255.f; float pb = ((float)((part.dcolour )&0xFF)) / 255.f; switch (deco_space) { case 0: // sRGB pa = (pa <= 0.04045f) ? (pa / 12.92f) : pow((pa + 0.055f) / 1.055f, 2.4f); pr = (pr <= 0.04045f) ? (pr / 12.92f) : pow((pr + 0.055f) / 1.055f, 2.4f); pg = (pg <= 0.04045f) ? (pg / 12.92f) : pow((pg + 0.055f) / 1.055f, 2.4f); pb = (pb <= 0.04045f) ? (pb / 12.92f) : pow((pb + 0.055f) / 1.055f, 2.4f); break; case 1: // linear break; case 2: // Gamma = 2.2 pa = pow(pa, 2.2f); pr = pow(pr, 2.2f); pg = pow(pg, 2.2f); pb = pow(pb, 2.2f); break; case 3: // Gamma = 1.8 pa = pow(pa, 1.8f); pr = pow(pr, 1.8f); pg = pow(pg, 1.8f); pb = pow(pb, 1.8f); break; } tas += pa; trs += pr; tgs += pg; tbs += pb; } } if (num == 0) return; ta = tas / num; tr = trs / num; tg = tgs / num; tb = tbs / num; switch (deco_space) { case 0: // sRGB ta = (ta <= 0.0031308f) ? (ta * 12.92f) : (1.055f * pow(ta, 1.f / 2.4f) - 0.055f); tr = (tr <= 0.0031308f) ? (tr * 12.92f) : (1.055f * pow(tr, 1.f / 2.4f) - 0.055f); tg = (tg <= 0.0031308f) ? (tg * 12.92f) : (1.055f * pow(tg, 1.f / 2.4f) - 0.055f); tb = (tb <= 0.0031308f) ? (tb * 12.92f) : (1.055f * pow(tb, 1.f / 2.4f) - 0.055f); break; case 1: // linear break; case 2: // Gamma = 2.2 ta = pow(ta, 1.f / 2.2f); tr = pow(tr, 1.f / 2.2f); tg = pow(tg, 1.f / 2.2f); tb = pow(tb, 1.f / 2.2f); break; case 3: // Gamma = 1.8 ta = pow(ta, 1.f / 1.8f); tr = pow(tr, 1.f / 1.8f); tg = pow(tg, 1.f / 1.8f); tb = pow(tb, 1.f / 1.8f); break; } if (!parts[ID(rp)].dcolour) ta -= 3/255.0f; } } ta *= 255.0f; tr *= 255.0f; tg *= 255.0f; tb *= 255.0f; ta += .5f; tr += .5f; tg += .5f; tb += .5f; colA_ = ta; colR_ = tr; colG_ = tg; colB_ = tb; if(colA_ > 255) colA_ = 255; else if(colA_ < 0) colA_ = 0; if(colR_ > 255) colR_ = 255; else if(colR_ < 0) colR_ = 0; if(colG_ > 255) colG_ = 255; else if(colG_ < 0) colG_ = 0; if(colB_ > 255) colB_ = 255; else if(colB_ < 0) colB_ = 0; parts[ID(rp)].dcolour = ((colA_<<24)|(colR_<<16)|(colG_<<8)|colB_); } void Simulation::ApplyDecorationPoint(int positionX, int positionY, int colR, int colG, int colB, int colA, int mode, Brush * cBrush) { if(cBrush) { int radiusX = cBrush->GetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y; unsigned char *bitmap = cBrush->GetBitmap(); for(int y = 0; y < sizeY; y++) { for(int x = 0; x < sizeX; x++) { if(bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES)) { ApplyDecoration(positionX+(x-radiusX), positionY+(y-radiusY), colR, colG, colB, colA, mode); } } } } } void Simulation::ApplyDecorationLine(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode, Brush * cBrush) { bool reverseXY = abs(y2-y1) > abs(x2-x1); int x, y, dx, dy, sy, rx = 0, ry = 0; float e = 0.0f, de; if(cBrush) { rx = cBrush->GetRadius().X; ry = cBrush->GetRadius().Y; } if (reverseXY) { y = x1; x1 = y1; y1 = y; y = x2; x2 = y2; y2 = y; } if (x1 > x2) { y = x1; x1 = x2; x2 = y; y = y1; y1 = y2; y2 = y; } dx = x2 - x1; dy = abs(y2 - y1); if (dx) de = dy/(float)dx; else de = 0.0f; y = y1; sy = (y1= 0.5f) { y += sy; if (!(rx+ry)) { if (reverseXY) ApplyDecorationPoint(y, x, colR, colG, colB, colA, mode, cBrush); else ApplyDecorationPoint(x, y, colR, colG, colB, colA, mode, cBrush); } e -= 1.0f; } } } void Simulation::ApplyDecorationBox(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode) { int i, j; if (x1>x2) { i = x2; x2 = x1; x1 = i; } if (y1>y2) { j = y2; y2 = y1; y1 = j; } for (j=y1; j<=y2; j++) for (i=x1; i<=x2; i++) ApplyDecoration(i, j, colR, colG, colB, colA, mode); } bool Simulation::ColorCompare(Renderer *ren, int x, int y, int replaceR, int replaceG, int replaceB) { pixel pix = ren->vid[x+y*WINDOWW]; int r = PIXR(pix); int g = PIXG(pix); int b = PIXB(pix); int diff = std::abs(replaceR-r) + std::abs(replaceG-g) + std::abs(replaceB-b); return diff < 15; } void Simulation::ApplyDecorationFill(Renderer *ren, int x, int y, int colR, int colG, int colB, int colA, int replaceR, int replaceG, int replaceB) { int x1, x2; char *bitmap = (char*)malloc(XRES*YRES); //Bitmap for checking if (!bitmap) return; memset(bitmap, 0, XRES*YRES); if (!ColorCompare(ren, x, y, replaceR, replaceG, replaceB)) { free(bitmap); return; } try { CoordStack& cs = getCoordStackSingleton(); cs.clear(); cs.push(x, y); do { cs.pop(x, y); x1 = x2 = x; // go left as far as possible while (x1>0) { if (bitmap[(x1-1)+y*XRES] || !ColorCompare(ren, x1-1, y, replaceR, replaceG, replaceB)) { break; } x1--; } // go right as far as possible while (x2= 1) for (x=x1; x<=x2; x++) if (!bitmap[x+(y-1)*XRES] && ColorCompare(ren, x, y-1, replaceR, replaceG, replaceB)) cs.push(x, y-1); if (y < YRES-1) for (x=x1; x<=x2; x++) if (!bitmap[x+(y+1)*XRES] && ColorCompare(ren, x, y+1, replaceR, replaceG, replaceB)) cs.push(x, y+1); } while (cs.getSize() > 0); } catch (std::exception& e) { std::cerr << e.what() << std::endl; free(bitmap); return; } free(bitmap); } #endif int Simulation::Tool(int x, int y, int tool, int brushX, int brushY, float strength) { Particle * cpart = NULL; int r; if ((r = pmap[y][x])) cpart = &(parts[ID(r)]); else if ((r = photons[y][x])) cpart = &(parts[ID(r)]); return tools[tool].Perform(this, cpart, x, y, brushX, brushY, strength); } #ifndef RENDERER int Simulation::ToolBrush(int positionX, int positionY, int tool, Brush * cBrush, float strength) { if(cBrush) { int radiusX = cBrush->GetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y; unsigned char *bitmap = cBrush->GetBitmap(); for(int y = 0; y < sizeY; y++) for(int x = 0; x < sizeX; x++) if(bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES)) Tool(positionX + (x - radiusX), positionY + (y - radiusY), tool, positionX, positionY, strength); } return 0; } void Simulation::ToolLine(int x1, int y1, int x2, int y2, int tool, Brush * cBrush, float strength) { bool reverseXY = abs(y2-y1) > abs(x2-x1); int x, y, dx, dy, sy, rx = cBrush->GetRadius().X, ry = cBrush->GetRadius().Y; float e = 0.0f, de; if (reverseXY) { y = x1; x1 = y1; y1 = y; y = x2; x2 = y2; y2 = y; } if (x1 > x2) { y = x1; x1 = x2; x2 = y; y = y1; y1 = y2; y2 = y; } dx = x2 - x1; dy = abs(y2 - y1); if (dx) de = dy/(float)dx; else de = 0.0f; y = y1; sy = (y1= 0.5f) { y += sy; if (!(rx+ry) && ((y1=y2))) { if (reverseXY) ToolBrush(y, x, tool, cBrush, strength); else ToolBrush(x, y, tool, cBrush, strength); } e -= 1.0f; } } } void Simulation::ToolBox(int x1, int y1, int x2, int y2, int tool, float strength) { int brushX, brushY; brushX = ((x1 + x2) / 2); brushY = ((y1 + y2) / 2); int i, j; if (x1>x2) { i = x2; x2 = x1; x1 = i; } if (y1>y2) { j = y2; y2 = y1; y1 = j; } for (j=y1; j<=y2; j++) for (i=x1; i<=x2; i++) Tool(i, j, tool, brushX, brushY, strength); } #endif int Simulation::CreateWalls(int x, int y, int rx, int ry, int wall, Brush * cBrush) { if(cBrush) { rx = cBrush->GetRadius().X; ry = cBrush->GetRadius().Y; } ry = ry/CELL; rx = rx/CELL; x = x/CELL; y = y/CELL; x -= rx; y -= ry; for (int wallX = x; wallX <= x+rx+rx; wallX++) { for (int wallY = y; wallY <= y+ry+ry; wallY++) { if (wallX >= 0 && wallX < XRES/CELL && wallY >= 0 && wallY < YRES/CELL) { if (wall == WL_FAN) { fvx[wallY][wallX] = 0.0f; fvy[wallY][wallX] = 0.0f; } else if (wall == WL_STREAM) { wallX = x + rx; wallY = y + ry; //streamlines can't be drawn next to each other for (int tempY = wallY-1; tempY < wallY+2; tempY++) for (int tempX = wallX-1; tempX < wallX+2; tempX++) { if (tempX >= 0 && tempX < XRES/CELL && tempY >= 0 && tempY < YRES/CELL && bmap[tempY][tempX] == WL_STREAM) return 1; } } if (wall == WL_GRAV || bmap[wallY][wallX] == WL_GRAV) gravWallChanged = true; if (wall == WL_ERASEALL) { for (int i = 0; i < CELL; i++) for (int j = 0; j < CELL; j++) { delete_part(wallX*CELL+i, wallY*CELL+j); } for (int i = signs.size()-1; i >= 0; i--) if (signs[i].x >= wallX*CELL && signs[i].y >= wallY*CELL && signs[i].x <= (wallX+1)*CELL && signs[i].y <= (wallY+1)*CELL) signs.erase(signs.begin()+i); bmap[wallY][wallX] = 0; } else bmap[wallY][wallX] = wall; } } } return 1; } void Simulation::CreateWallLine(int x1, int y1, int x2, int y2, int rx, int ry, int wall, Brush * cBrush) { int x, y, dx, dy, sy; bool reverseXY = abs(y2-y1) > abs(x2-x1); float e = 0.0f, de; if (reverseXY) { y = x1; x1 = y1; y1 = y; y = x2; x2 = y2; y2 = y; } if (x1 > x2) { y = x1; x1 = x2; x2 = y; y = y1; y1 = y2; y2 = y; } dx = x2 - x1; dy = abs(y2 - y1); if (dx) de = dy/(float)dx; else de = 0.0f; y = y1; sy = (y1= 0.5f) { y += sy; if ((y1=y2)) { if (reverseXY) CreateWalls(y, x, rx, ry, wall, cBrush); else CreateWalls(x, y, rx, ry, wall, cBrush); } e -= 1.0f; } } } void Simulation::CreateWallBox(int x1, int y1, int x2, int y2, int wall) { int i, j; if (x1>x2) { i = x2; x2 = x1; x1 = i; } if (y1>y2) { j = y2; y2 = y1; y1 = j; } for (j=y1; j<=y2; j++) for (i=x1; i<=x2; i++) CreateWalls(i, j, 0, 0, wall, NULL); } int Simulation::FloodWalls(int x, int y, int wall, int bm) { int x1, x2, dy = CELL; if (bm==-1) { if (wall==WL_ERASE || wall==WL_ERASEALL) { bm = bmap[y/CELL][x/CELL]; if (!bm) return 0; } else bm = 0; } if (bmap[y/CELL][x/CELL]!=bm) return 1; // go left as far as possible x1 = x2 = x; while (x1>=CELL) { if (bmap[y/CELL][(x1-1)/CELL]!=bm) { break; } x1--; } while (x2=CELL) for (x=x1; x<=x2; x++) if (bmap[(y-dy)/CELL][x/CELL]==bm) if (!FloodWalls(x, y-dy, wall, bm)) return 0; if (yGetRadius().X, radiusY = cBrush->GetRadius().Y, sizeX = cBrush->GetSize().X, sizeY = cBrush->GetSize().Y; unsigned char *bitmap = cBrush->GetBitmap(); // special case for LIGH if (c == PT_LIGH) { if (currentTick < lightningRecreate) return 1; int newlife = radiusX + radiusY; if (newlife > 55) newlife = 55; c = PMAP(newlife, c); lightningRecreate = currentTick+newlife/4; return CreatePartFlags(positionX, positionY, c, flags); } else if (c == PT_TESC) { int newtmp = (radiusX*4+radiusY*4+7); if (newtmp > 300) newtmp = 300; c = PMAP(newtmp, c); } for (int y = sizeY-1; y >=0; y--) { for (int x = 0; x < sizeX; x++) { if (bitmap[(y*sizeX)+x] && (positionX+(x-radiusX) >= 0 && positionY+(y-radiusY) >= 0 && positionX+(x-radiusX) < XRES && positionY+(y-radiusY) < YRES)) { CreatePartFlags(positionX+(x-radiusX), positionY+(y-radiusY), c, flags); } } } } return 0; } int Simulation::CreateParts(int x, int y, int rx, int ry, int c, int flags) { bool created = false; if (flags == -1) flags = replaceModeFlags; // special case for LIGH if (c == PT_LIGH) { if (currentTick < lightningRecreate) return 1; int newlife = rx + ry; if (newlife > 55) newlife = 55; c = PMAP(newlife, c); lightningRecreate = currentTick+newlife/4; rx = ry = 0; } else if (c == PT_TESC) { int newtmp = (rx*4+ry*4+7); if (newtmp > 300) newtmp = 300; c = PMAP(newtmp, c); } for (int j = -ry; j <= ry; j++) for (int i = -rx; i <= rx; i++) if (CreatePartFlags(x+i, y+j, c, flags)) created = true; return !created; } void Simulation::CreateLine(int x1, int y1, int x2, int y2, int c, Brush * cBrush, int flags) { int x, y, dx, dy, sy, rx = cBrush->GetRadius().X, ry = cBrush->GetRadius().Y; bool reverseXY = abs(y2-y1) > abs(x2-x1); float e = 0.0f, de; if (reverseXY) { y = x1; x1 = y1; y1 = y; y = x2; x2 = y2; y2 = y; } if (x1 > x2) { y = x1; x1 = x2; x2 = y; y = y1; y1 = y2; y2 = y; } dx = x2 - x1; dy = abs(y2 - y1); if (dx) de = dy/(float)dx; else de = 0.0f; y = y1; sy = (y1= 0.5f) { y += sy; if (!(rx+ry) && ((y1=y2))) { if (reverseXY) CreateParts(y, x, c, cBrush, flags); else CreateParts(x, y, c, cBrush, flags); } e -= 1.0f; } } } #endif int Simulation::CreatePartFlags(int x, int y, int c, int flags) { if (x < 0 || y < 0 || x >= XRES || y >= YRES) { return 0; } if (flags & REPLACE_MODE) { // if replace whatever and there's something to replace // or replace X and there's a non-energy particle on top with type X // or replace X and there's an energy particle on top with type X if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) || (!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) || (photons[y][x] && TYP(photons[y][x]) == replaceModeSelected)) { if (c) create_part(photons[y][x] ? ID(photons[y][x]) : ID(pmap[y][x]), x, y, TYP(c), ID(c)); else delete_part(x, y); } return 0; } else if (!c) { delete_part(x, y); return 0; } else if (flags & SPECIFIC_DELETE) { // if delete whatever and there's something to delete // or delete X and there's a non-energy particle on top with type X // or delete X and there's an energy particle on top with type X if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) || (!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) || (photons[y][x] && TYP(photons[y][x]) == replaceModeSelected)) { delete_part(x, y); } return 0; } else { if (create_part(-2, x, y, TYP(c), ID(c)) == -1) { return 1; } return 0; } // I'm sure at least one compiler exists that would complain if this wasn't here return 0; } //Now simply creates a 0 pixel radius line without all the complicated flags / other checks void Simulation::CreateLine(int x1, int y1, int x2, int y2, int c) { bool reverseXY = abs(y2-y1) > abs(x2-x1); int x, y, dx, dy, sy; float e, de; int v = ID(c); c = TYP(c); if (reverseXY) { y = x1; x1 = y1; y1 = y; y = x2; x2 = y2; y2 = y; } if (x1 > x2) { y = x1; x1 = x2; x2 = y; y = y1; y1 = y2; y2 = y; } dx = x2 - x1; dy = abs(y2 - y1); e = 0.0f; if (dx) de = dy/(float)dx; else de = 0.0f; y = y1; sy = (y1= 0.5f) { y += sy; if ((y1=y2)) { if (reverseXY) create_part(-1, y, x, c, v); else create_part(-1, x, y, c, v); } e -= 1.0f; } } } #ifndef RENDERER void Simulation::CreateBox(int x1, int y1, int x2, int y2, int c, int flags) { int i, j; if (x1>x2) { i = x2; x2 = x1; x1 = i; } if (y1>y2) { j = y2; y2 = y1; y1 = j; } for (j=y2; j>=y1; j--) for (i=x1; i<=x2; i++) CreateParts(i, j, 0, 0, c, flags); } int Simulation::FloodParts(int x, int y, int fullc, int cm, int flags) { int c = TYP(fullc); int x1, x2, dy = (c= XRES-CELL || y < CELL || y >= YRES-CELL || c == PT_SPRK)) return 1; else if (x < 0 || x >= XRES || y < 0 || y >= YRES) return 1; if (c == 0) { cm = TYP(pmap[y][x]); if (!cm) { cm = TYP(photons[y][x]); if (!cm) { if (bmap[y/CELL][x/CELL]) return FloodWalls(x, y, WL_ERASE, -1); else return -1; } } } else cm = 0; } if (c != 0 && IsWallBlocking(x, y, c)) return 1; if (!FloodFillPmapCheck(x, y, cm)) return 1; coord_stack = (short unsigned int (*)[2])malloc(sizeof(unsigned short)*2*coord_stack_limit); coord_stack[coord_stack_size][0] = x; coord_stack[coord_stack_size][1] = y; coord_stack_size++; do { coord_stack_size--; x = coord_stack[coord_stack_size][0]; y = coord_stack[coord_stack_size][1]; x1 = x2 = x; // go left as far as possible while (c?x1>CELL:x1>0) { if (!FloodFillPmapCheck(x1-1, y, cm) || (c != 0 && IsWallBlocking(x1-1, y, c))) { break; } x1--; } // go right as far as possible while (c?x2=CELL+dy:y>=dy) for (x=x1; x<=x2; x++) if (FloodFillPmapCheck(x, y-dy, cm) && (c == 0 || !IsWallBlocking(x, y-dy, c))) { coord_stack[coord_stack_size][0] = x; coord_stack[coord_stack_size][1] = y-dy; coord_stack_size++; if (coord_stack_size>=coord_stack_limit) { free(coord_stack); return -1; } } if (c?y=coord_stack_limit) { free(coord_stack); return -1; } } } while (coord_stack_size>0); free(coord_stack); return created_something; } #endif void Simulation::orbitalparts_get(int block1, int block2, int resblock1[], int resblock2[]) { resblock1[0] = (block1&0x000000FF); resblock1[1] = (block1&0x0000FF00)>>8; resblock1[2] = (block1&0x00FF0000)>>16; resblock1[3] = (block1&0xFF000000)>>24; resblock2[0] = (block2&0x000000FF); resblock2[1] = (block2&0x0000FF00)>>8; resblock2[2] = (block2&0x00FF0000)>>16; resblock2[3] = (block2&0xFF000000)>>24; } void Simulation::orbitalparts_set(int *block1, int *block2, int resblock1[], int resblock2[]) { int block1tmp = 0; int block2tmp = 0; block1tmp = (resblock1[0]&0xFF); block1tmp |= (resblock1[1]&0xFF)<<8; block1tmp |= (resblock1[2]&0xFF)<<16; block1tmp |= (resblock1[3]&0xFF)<<24; block2tmp = (resblock2[0]&0xFF); block2tmp |= (resblock2[1]&0xFF)<<8; block2tmp |= (resblock2[2]&0xFF)<<16; block2tmp |= (resblock2[3]&0xFF)<<24; *block1 = block1tmp; *block2 = block2tmp; } inline int Simulation::is_wire(int x, int y) { 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; } inline int Simulation::is_wire_off(int x, int y) { 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; } // implement __builtin_ctz and __builtin_clz on msvc #ifdef _MSC_VER unsigned msvc_ctz(unsigned a) { unsigned long i; _BitScanForward(&i, a); return i; } unsigned msvc_clz(unsigned a) { unsigned long i; _BitScanReverse(&i, a); return 31 - i; } #define __builtin_ctz msvc_ctz #define __builtin_clz msvc_clz #endif int Simulation::get_wavelength_bin(int *wm) { int i, w0, wM, r; if (!(*wm & 0x3FFFFFFF)) return -1; #if defined(__GNUC__) || defined(_MSVC_VER) w0 = __builtin_ctz(*wm | 0xC0000000); wM = 31 - __builtin_clz(*wm & 0x3FFFFFFF); #else w0 = 30; wM = 0; for (i = 0; i < 30; i++) if (*wm & (1< wM) wM = i; } #endif if (wM - w0 < 5) return wM + w0; r = RNG::Ref().gen(); i = (r >> 1) % (wM-w0-4); i += w0; if (r & 1) { *wm &= 0x1F << i; return (i + 2) * 2; } else { *wm &= 0xF << i; return (i + 2) * 2 - 1; } } void Simulation::set_emap(int x, int y) { int x1, x2; if (!is_wire_off(x, y)) return; // go left as far as possible x1 = x2 = x; while (x1>0) { if (!is_wire_off(x1-1, y)) break; x1--; } while (x21 && x1==x2 && is_wire(x1-1, y-1) && is_wire(x1, y-1) && is_wire(x1+1, y-1) && !is_wire(x1-1, y-2) && is_wire(x1, y-2) && !is_wire(x1+1, y-2)) set_emap(x1, y-2); else if (y>0) for (x=x1; x<=x2; x++) if (is_wire_off(x, y-1)) { if (x==x1 || x==x2 || y>=YRES/CELL-1 || is_wire(x-1, y-1) || is_wire(x+1, y-1) || is_wire(x-1, y+1) || !is_wire(x, y+1) || is_wire(x+1, y+1)) set_emap(x, y-1); } if (yClear(); if(air) { air->Clear(); air->ClearAirH(); } SetEdgeMode(edgeMode); } bool Simulation::IsWallBlocking(int x, int y, int type) { if (bmap[y/CELL][x/CELL]) { int wall = bmap[y/CELL][x/CELL]; if (wall == WL_ALLOWGAS && !(elements[type].Properties&TYPE_GAS)) return true; else if (wall == WL_ALLOWENERGY && !(elements[type].Properties&TYPE_ENERGY)) return true; else if (wall == WL_ALLOWLIQUID && !(elements[type].Properties&TYPE_LIQUID)) return true; else if (wall == WL_ALLOWPOWDER && !(elements[type].Properties&TYPE_PART)) return true; else if (wall == WL_ALLOWAIR || wall == WL_WALL || wall == WL_WALLELEC) return true; else if (wall == WL_EWALL && !emap[y/CELL][x/CELL]) return true; } return false; } void Simulation::init_can_move() { int movingType, destinationType; // can_move[moving type][type at destination] // 0 = No move/Bounce // 1 = Swap // 2 = Both particles occupy the same space. // 3 = Varies, go run some extra checks //particles that don't exist shouldn't move... for (destinationType = 0; destinationType < PT_NUM; destinationType++) can_move[0][destinationType] = 0; //initialize everything else to swapping by default for (movingType = 1; movingType < PT_NUM; movingType++) for (destinationType = 0; destinationType < PT_NUM; destinationType++) can_move[movingType][destinationType] = 1; //photons go through everything by default for (destinationType = 1; destinationType < PT_NUM; destinationType++) can_move[PT_PHOT][destinationType] = 2; for (movingType = 1; movingType < PT_NUM; movingType++) { for (destinationType = 1; destinationType < PT_NUM; destinationType++) { //weight check, also prevents particles of same type displacing each other if (elements[movingType].Weight <= elements[destinationType].Weight || destinationType == PT_GEL) can_move[movingType][destinationType] = 0; //other checks for NEUT and energy particles if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTPASS)) can_move[movingType][destinationType] = 2; if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTABSORB)) can_move[movingType][destinationType] = 1; if (movingType == PT_NEUT && (elements[destinationType].Properties&PROP_NEUTPENETRATE)) can_move[movingType][destinationType] = 1; if (destinationType == PT_NEUT && (elements[movingType].Properties&PROP_NEUTPENETRATE)) can_move[movingType][destinationType] = 0; if ((elements[movingType].Properties&TYPE_ENERGY) && (elements[destinationType].Properties&TYPE_ENERGY)) can_move[movingType][destinationType] = 2; } } for (destinationType = 0; destinationType < PT_NUM; destinationType++) { //set what stickmen can move through int stkm_move = 0; if (elements[destinationType].Properties & (TYPE_LIQUID | TYPE_GAS)) stkm_move = 2; if (!destinationType || destinationType == PT_PRTO || destinationType == PT_SPAWN || destinationType == PT_SPAWN2) stkm_move = 2; can_move[PT_STKM][destinationType] = stkm_move; can_move[PT_STKM2][destinationType] = stkm_move; can_move[PT_FIGH][destinationType] = stkm_move; //spark shouldn't move can_move[PT_SPRK][destinationType] = 0; } for (movingType = 1; movingType < PT_NUM; movingType++) { //everything "swaps" with VACU and BHOL to make them eat things can_move[movingType][PT_BHOL] = 1; can_move[movingType][PT_NBHL] = 1; //nothing goes through stickmen can_move[movingType][PT_STKM] = 0; can_move[movingType][PT_STKM2] = 0; can_move[movingType][PT_FIGH] = 0; //INVS behaviour varies with pressure can_move[movingType][PT_INVIS] = 3; //stop CNCT and ROCK from being displaced by other particles can_move[movingType][PT_CNCT] = 0; can_move[movingType][PT_ROCK] = 0; //VOID and PVOD behaviour varies with powered state and ctype can_move[movingType][PT_PVOD] = 3; can_move[movingType][PT_VOID] = 3; //nothing moves through EMBR (not sure why, but it's killed when it touches anything) can_move[movingType][PT_EMBR] = 0; can_move[PT_EMBR][movingType] = 0; //Energy particles move through VIBR and BVBR, so it can absorb them if (elements[movingType].Properties & TYPE_ENERGY) { can_move[movingType][PT_VIBR] = 1; can_move[movingType][PT_BVBR] = 1; } //SAWD cannot be displaced by other powders if (elements[movingType].Properties & TYPE_PART) can_move[movingType][PT_SAWD] = 0; } //a list of lots of things PHOT can move through // TODO: replace with property for (destinationType = 0; destinationType < PT_NUM; destinationType++) { if (destinationType == PT_GLAS || destinationType == PT_PHOT || destinationType == PT_FILT || destinationType == PT_INVIS || destinationType == PT_CLNE || destinationType == PT_PCLN || destinationType == PT_BCLN || destinationType == PT_PBCN || destinationType == PT_WATR || destinationType == PT_DSTW || destinationType == PT_SLTW || destinationType == PT_GLOW || destinationType == PT_ISOZ || destinationType == PT_ISZS || destinationType == PT_QRTZ || destinationType == PT_PQRT || destinationType == PT_H2 || destinationType == PT_BGLA || destinationType == PT_C5) can_move[PT_PHOT][destinationType] = 2; 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) { can_move[PT_PROT][destinationType] = 2; can_move[PT_GRVT][destinationType] = 2; } } //other special cases that weren't covered above can_move[PT_DEST][PT_DMND] = 0; can_move[PT_DEST][PT_CLNE] = 0; can_move[PT_DEST][PT_PCLN] = 0; can_move[PT_DEST][PT_BCLN] = 0; can_move[PT_DEST][PT_PBCN] = 0; can_move[PT_NEUT][PT_INVIS] = 2; can_move[PT_ELEC][PT_LCRY] = 2; can_move[PT_ELEC][PT_EXOT] = 2; can_move[PT_ELEC][PT_GLOW] = 2; can_move[PT_PHOT][PT_LCRY] = 3; //varies according to LCRY life can_move[PT_PHOT][PT_GPMP] = 3; can_move[PT_PHOT][PT_BIZR] = 2; can_move[PT_ELEC][PT_BIZR] = 2; can_move[PT_PHOT][PT_BIZRG] = 2; can_move[PT_ELEC][PT_BIZRG] = 2; can_move[PT_PHOT][PT_BIZRS] = 2; can_move[PT_ELEC][PT_BIZRS] = 2; can_move[PT_BIZR][PT_FILT] = 2; can_move[PT_BIZRG][PT_FILT] = 2; can_move[PT_ANAR][PT_WHOL] = 1; //WHOL eats ANAR can_move[PT_ANAR][PT_NWHL] = 1; can_move[PT_ELEC][PT_DEUT] = 1; can_move[PT_THDR][PT_THDR] = 2; can_move[PT_EMBR][PT_EMBR] = 2; can_move[PT_TRON][PT_SWCH] = 3; } /* RETURN-value explanation 1 = Swap 0 = No move/Bounce 2 = Both particles occupy the same space. */ int Simulation::eval_move(int pt, int nx, int ny, unsigned *rr) { unsigned r; int result; if (nx<0 || ny<0 || nx>=XRES || ny>=YRES) return 0; r = pmap[ny][nx]; if (r) r = (r&~PMAPMASK) | parts[ID(r)].type; if (rr) *rr = r; if (pt>=PT_NUM || TYP(r)>=PT_NUM) return 0; result = can_move[pt][TYP(r)]; if (result == 3) { switch (TYP(r)) { case PT_LCRY: if (pt==PT_PHOT) result = (parts[ID(r)].life > 5)? 2 : 0; break; case PT_GPMP: if (pt == PT_PHOT) result = (parts[ID(r)].life < 10) ? 2 : 0; break; case PT_INVIS: { float pressureResistance = 0.0f; if (parts[ID(r)].tmp > 0) pressureResistance = (float)parts[ID(r)].tmp; else pressureResistance = 4.0f; if (pv[ny/CELL][nx/CELL] < -pressureResistance || pv[ny/CELL][nx/CELL] > pressureResistance) result = 2; else result = 0; break; } case PT_PVOD: if (parts[ID(r)].life == 10) { if (!parts[ID(r)].ctype || (parts[ID(r)].ctype==pt)!=(parts[ID(r)].tmp&1)) result = 1; else result = 0; } else result = 0; break; case PT_VOID: if (!parts[ID(r)].ctype || (parts[ID(r)].ctype==pt)!=(parts[ID(r)].tmp&1)) result = 1; else result = 0; break; case PT_SWCH: if (pt == PT_TRON) { if (parts[ID(r)].life >= 10) return 2; else return 0; } break; default: // This should never happen // If it were to happen, try_move would interpret a 3 as a 1 result = 1; } } if (bmap[ny/CELL][nx/CELL]) { if (IsWallBlocking(nx, ny, pt)) return 0; if (bmap[ny/CELL][nx/CELL]==WL_EHOLE && !emap[ny/CELL][nx/CELL] && !(elements[pt].Properties&TYPE_SOLID) && !(elements[TYP(r)].Properties&TYPE_SOLID)) return 2; } return result; } int Simulation::try_move(int i, int x, int y, int nx, int ny) { unsigned r = 0, e; if (x==nx && y==ny) return 1; if (nx<0 || ny<0 || nx>=XRES || ny>=YRES) return 1; e = eval_move(parts[i].type, nx, ny, &r); /* half-silvered mirror */ if (!e && parts[i].type==PT_PHOT && ((TYP(r)==PT_BMTL && RNG::Ref().chance(1, 2)) || TYP(pmap[y][x])==PT_BMTL)) e = 2; if (!e) //if no movement { int rt = TYP(r); if (rt == PT_WOOD) { float vel = std::sqrt(std::pow(parts[i].vx, 2) + std::pow(parts[i].vy, 2)); if (vel > 5) part_change_type(ID(r), nx, ny, PT_SAWD); } if (!(elements[parts[i].type].Properties & TYPE_ENERGY)) return 0; if (!legacy_enable && parts[i].type==PT_PHOT && r)//PHOT heat conduction { if (rt == PT_COAL || rt == PT_BCOL) parts[ID(r)].temp = parts[i].temp; if (rt < PT_NUM && elements[rt].HeatConduct && (rt!=PT_HSWC||parts[ID(r)].life==10) && rt!=PT_FILT) parts[i].temp = parts[ID(r)].temp = restrict_flt((parts[ID(r)].temp+parts[i].temp)/2, MIN_TEMP, MAX_TEMP); } else if ((parts[i].type==PT_NEUT || parts[i].type==PT_ELEC) && (rt==PT_CLNE || rt==PT_PCLN || rt==PT_BCLN || rt==PT_PBCN)) { if (!parts[ID(r)].ctype) parts[ID(r)].ctype = parts[i].type; } if (rt==PT_PRTI && (elements[parts[i].type].Properties & TYPE_ENERGY)) { int nnx, count; for (count=0; count<8; count++) { if (isign(x-nx)==isign(portal_rx[count]) && isign(y-ny)==isign(portal_ry[count])) break; } count = count%8; parts[ID(r)].tmp = (int)((parts[ID(r)].temp-73.15f)/100+1); if (parts[ID(r)].tmp>=CHANNELS) parts[ID(r)].tmp = CHANNELS-1; else if (parts[ID(r)].tmp<0) parts[ID(r)].tmp = 0; for ( nnx=0; nnx<80; nnx++) if (!portalp[parts[ID(r)].tmp][count][nnx].type) { portalp[parts[ID(r)].tmp][count][nnx] = parts[i]; parts[i].type=PT_NONE; break; } } return 0; } int Element_FILT_interactWavelengths(Particle* cpart, int origWl); if (e == 2) //if occupy same space { switch (parts[i].type) { case PT_PHOT: { switch (TYP(r)) { case PT_GLOW: if (!parts[ID(r)].life && RNG::Ref().chance(29, 30)) { parts[ID(r)].life = 120; create_gain_photon(i); } break; case PT_FILT: parts[i].ctype = Element_FILT_interactWavelengths(&parts[ID(r)], parts[i].ctype); break; case PT_C5: if (parts[ID(r)].life > 0 && (parts[ID(r)].ctype & parts[i].ctype & 0xFFFFFFC0)) { float vx = ((parts[ID(r)].tmp << 16) >> 16) / 255.0f; float vy = (parts[ID(r)].tmp >> 16) / 255.0f; float vn = parts[i].vx * parts[i].vx + parts[i].vy * parts[i].vy; // if the resulting velocity would be 0, that would cause division by 0 inside the else // shoot the photon off at a 90 degree angle instead (probably particle order dependent) if (parts[i].vx + vx == 0 && parts[i].vy + vy == 0) { parts[i].vx = vy; parts[i].vy = -vx; } else { parts[i].ctype = (parts[ID(r)].ctype & parts[i].ctype) >> 6; // add momentum of photons to each other parts[i].vx += vx; parts[i].vy += vy; // normalize velocity to original value vn /= parts[i].vx * parts[i].vx + parts[i].vy * parts[i].vy; vn = sqrtf(vn); parts[i].vx *= vn; parts[i].vy *= vn; } parts[ID(r)].life = 0; parts[ID(r)].ctype = 0; } else if(!parts[ID(r)].ctype && parts[i].ctype & 0xFFFFFFC0) { parts[ID(r)].life = 1; parts[ID(r)].ctype = parts[i].ctype; parts[ID(r)].tmp = (0xFFFF & (int)(parts[i].vx * 255.0f)) | (0xFFFF0000 & (int)(parts[i].vy * 16711680.0f)); parts[ID(r)].tmp2 = (0xFFFF & (int)((parts[i].x - x) * 255.0f)) | (0xFFFF0000 & (int)((parts[i].y - y) * 16711680.0f)); kill_part(i); } break; case PT_INVIS: { float pressureResistance = 0.0f; if (parts[ID(r)].tmp > 0) pressureResistance = (float)parts[ID(r)].tmp; else pressureResistance = 4.0f; if (pv[ny/CELL][nx/CELL] >= -pressureResistance && pv[ny/CELL][nx/CELL] <= pressureResistance) { part_change_type(i,x,y,PT_NEUT); parts[i].ctype = 0; } break; } case PT_BIZR: case PT_BIZRG: case PT_BIZRS: part_change_type(i, x, y, PT_ELEC); parts[i].ctype = 0; break; case PT_H2: if (!(parts[i].tmp&0x1)) { part_change_type(i, x, y, PT_PROT); parts[i].ctype = 0; parts[i].tmp2 = 0x1; create_part(ID(r), x, y, PT_ELEC); return 1; } break; case PT_GPMP: if (parts[ID(r)].life == 0) { part_change_type(i, x, y, PT_GRVT); parts[i].tmp = parts[ID(r)].temp - 273.15f; } break; } break; } case PT_NEUT: if (TYP(r) == PT_GLAS || TYP(r) == PT_BGLA) if (RNG::Ref().chance(9, 10)) create_cherenkov_photon(i); break; case PT_ELEC: if (TYP(r) == PT_GLOW) { part_change_type(i, x, y, PT_PHOT); parts[i].ctype = 0x3FFFFFFF; } break; case PT_PROT: if (TYP(r) == PT_INVIS) part_change_type(i, x, y, PT_NEUT); break; case PT_BIZR: case PT_BIZRG: if (TYP(r) == PT_FILT) parts[i].ctype = Element_FILT_interactWavelengths(&parts[ID(r)], parts[i].ctype); break; } return 1; } //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 switch (TYP(r)) { case PT_VOID: case PT_PVOD: // this is where void eats particles // void ctype already checked in eval_move kill_part(i); return 0; case PT_BHOL: case PT_NBHL: // this is where blackhole eats particles if (!legacy_enable) { parts[ID(r)].temp = restrict_flt(parts[ID(r)].temp+parts[i].temp/2, MIN_TEMP, MAX_TEMP);//3.0f; } kill_part(i); return 0; case PT_WHOL: case PT_NWHL: // whitehole eats anar if (parts[i].type == PT_ANAR) { if (!legacy_enable) { parts[ID(r)].temp = restrict_flt(parts[ID(r)].temp - (MAX_TEMP-parts[i].temp)/2, MIN_TEMP, MAX_TEMP); } kill_part(i); return 0; } break; case PT_DEUT: if (parts[i].type == PT_ELEC) { if(parts[ID(r)].life < 6000) parts[ID(r)].life += 1; parts[ID(r)].temp = 0; kill_part(i); return 0; } break; case PT_VIBR: case PT_BVBR: if ((elements[parts[i].type].Properties & TYPE_ENERGY)) { parts[ID(r)].tmp += 20; kill_part(i); return 0; } break; } switch (parts[i].type) { case PT_NEUT: if (elements[TYP(r)].Properties & PROP_NEUTABSORB) { kill_part(i); return 0; } break; case PT_CNCT: if (y < ny && (TYP(pmap[y+1][x]) == PT_CNCT || TYP(pmap[y+1][x]) == PT_ROCK)) //check below CNCT for another CNCT or ROCK return 0; break; case PT_GBMB: if (parts[i].life > 0) return 0; break; } 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])) return 0; int ri = ID(r); //ri is the particle number at r (pmap[ny][nx]) if (r)//the swap part, if we make it this far, swap { if (parts[i].type==PT_NEUT) { // target material is NEUTPENETRATE, meaning it gets moved around when neutron passes unsigned s = pmap[y][x]; if (s && !(elements[TYP(s)].Properties&PROP_NEUTPENETRATE)) return 1; // if the element currently underneath neutron isn't NEUTPENETRATE, don't move anything except the neutron // if nothing is currently underneath neutron, only move target particle if(bmap[y/CELL][x/CELL] == WL_ALLOWENERGY) return 1; // do not drag target particle into an energy only wall if (s) { pmap[ny][nx] = (s&~PMAPMASK)|parts[ID(s)].type; parts[ID(s)].x = nx; parts[ID(s)].y = ny; } else pmap[ny][nx] = 0; parts[ri].x = x; parts[ri].y = y; pmap[y][x] = PMAP(ri, parts[ri].type); return 1; } if (ID(pmap[ny][nx]) == ri) pmap[ny][nx] = 0; parts[ri].x += x-nx; parts[ri].y += y-ny; pmap[(int)(parts[ri].y+0.5f)][(int)(parts[ri].x+0.5f)] = PMAP(ri, parts[ri].type); } return 1; } // try to move particle, and if successful update pmap and parts[i].x,y int Simulation::do_move(int i, int x, int y, float nxf, float nyf) { int nx = (int)(nxf+0.5f), ny = (int)(nyf+0.5f), result; if (edgeMode == 2) { bool x_ok = (nx >= CELL && nx < XRES-CELL); bool y_ok = (ny >= CELL && ny < YRES-CELL); if (!x_ok) nxf = remainder_p(nxf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f; if (!y_ok) nyf = remainder_p(nyf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f; nx = (int)(nxf+0.5f); ny = (int)(nyf+0.5f); /*if (!x_ok || !y_ok) { //make sure there isn't something blocking it on the other side //only needed if this if statement is moved after the try_move (like my mod) //if (!eval_move(t, nx, ny, NULL) || (t == PT_PHOT && pmap[ny][nx])) // return -1; }*/ } if (parts[i].type == PT_NONE) return 0; result = try_move(i, x, y, nx, ny); if (result) { int t = parts[i].type; parts[i].x = nxf; parts[i].y = nyf; if (ny!=y || nx!=x) { if (ID(pmap[y][x]) == i) pmap[y][x] = 0; if (ID(photons[y][x]) == i) photons[y][x] = 0; // kill_part if particle is out of bounds if (nx < CELL || nx >= XRES - CELL || ny < CELL || ny >= YRES - CELL) { kill_part(i); return -1; } if (elements[t].Properties & TYPE_ENERGY) photons[ny][nx] = PMAP(i, t); else if (t) pmap[ny][nx] = PMAP(i, t); } } return result; } void Simulation::photoelectric_effect(int nx, int ny)//create sparks from PHOT when hitting PSCN and NSCN { unsigned r = pmap[ny][nx]; if (TYP(r) == PT_PSCN) { if (TYP(pmap[ny][nx-1]) == PT_NSCN || TYP(pmap[ny][nx+1]) == PT_NSCN || TYP(pmap[ny-1][nx]) == PT_NSCN || TYP(pmap[ny+1][nx]) == PT_NSCN) { parts[ID(r)].ctype = PT_PSCN; part_change_type(ID(r), nx, ny, PT_SPRK); parts[ID(r)].life = 4; } } } unsigned Simulation::direction_to_map(float dx, float dy, int t) { // TODO: // Adding extra directions causes some inaccuracies. // Not adding them causes problems with some diagonal surfaces (photons absorbed instead of reflected). // For now, don't add them. // Solution may involve more intelligent setting of initial i0 value in find_next_boundary? // or rewriting normal/boundary finding code return (dx >= 0) | (((dx + dy) >= 0) << 1) | /* 567 */ ((dy >= 0) << 2) | /* 4+0 */ (((dy - dx) >= 0) << 3) | /* 321 */ ((dx <= 0) << 4) | (((dx + dy) <= 0) << 5) | ((dy <= 0) << 6) | (((dy - dx) <= 0) << 7); /* return (dx >= -0.001) | (((dx + dy) >= -0.001) << 1) | // 567 ((dy >= -0.001) << 2) | // 4+0 (((dy - dx) >= -0.001) << 3) | // 321 ((dx <= 0.001) << 4) | (((dx + dy) <= 0.001) << 5) | ((dy <= 0.001) << 6) | (((dy - dx) <= 0.001) << 7); }*/ } int Simulation::is_blocking(int t, int x, int y) { if (t & REFRACT) { if (x<0 || y<0 || x>=XRES || y>=YRES) return 0; if (TYP(pmap[y][x]) == PT_GLAS || TYP(pmap[y][x]) == PT_BGLA) return 1; return 0; } return !eval_move(t, x, y, NULL); } int Simulation::is_boundary(int pt, int x, int y) { if (!is_blocking(pt,x,y)) return 0; 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)) return 0; return 1; } int Simulation::find_next_boundary(int pt, int *x, int *y, int dm, int *em) { static int dx[8] = {1,1,0,-1,-1,-1,0,1}; static int dy[8] = {0,1,1,1,0,-1,-1,-1}; static int de[8] = {0x83,0x07,0x0E,0x1C,0x38,0x70,0xE0,0xC1}; int i, ii, i0; if (*x <= 0 || *x >= XRES-1 || *y <= 0 || *y >= YRES-1) return 0; if (*em != -1) { i0 = *em; dm &= de[i0]; } else i0 = 0; for (ii=0; ii<8; ii++) { i = (ii + i0) & 7; if ((dm & (1 << i)) && is_boundary(pt, *x+dx[i], *y+dy[i])) { *x += dx[i]; *y += dy[i]; *em = i; return 1; } } return 0; } int Simulation::get_normal(int pt, int x, int y, float dx, float dy, float *nx, float *ny) { int ldm, rdm, lm, rm; int lx, ly, lv, rx, ry, rv; int i, j; float r, ex, ey; if (!dx && !dy) return 0; if (!is_boundary(pt, x, y)) return 0; ldm = direction_to_map(-dy, dx, pt); rdm = direction_to_map(dy, -dx, pt); lx = rx = x; ly = ry = y; lv = rv = 1; lm = rm = -1; j = 0; for (i=0; i= NORMAL_INTERP) return 0; if (pt == PT_PHOT) photoelectric_effect(x, y); return get_normal(pt, x, y, dx, dy, nx, ny); } void Simulation::kill_part(int i)//kills particle number i { if (i < 0 || i >= NPART) return; int x = (int)(parts[i].x + 0.5f); int y = (int)(parts[i].y + 0.5f); int t = parts[i].type; if (t && elements[t].ChangeType) { (*(elements[t].ChangeType))(this, i, x, y, t, PT_NONE); } if (x >= 0 && y >= 0 && x < XRES && y < YRES) { if (ID(pmap[y][x]) == i) pmap[y][x] = 0; else if (ID(photons[y][x]) == i) photons[y][x] = 0; } // This shouldn't happen but ... you never know? if (t == PT_NONE) return; elementCount[t]--; parts[i].type = PT_NONE; parts[i].life = pfree; pfree = i; } // Changes the type of particle number i, to t. This also changes pmap at the same time // Returns true if the particle was killed bool Simulation::part_change_type(int i, int x, int y, int t) { if (x<0 || y<0 || x>=XRES || y>=YRES || i>=NPART || t<0 || t>=PT_NUM || !parts[i].type) return false; if (!elements[t].Enabled || t == PT_NONE) { kill_part(i); return true; } if (elements[t].CreateAllowed) { if (!(*(elements[t].CreateAllowed))(this, i, x, y, t)) return false; } if (elements[parts[i].type].ChangeType) (*(elements[parts[i].type].ChangeType))(this, i, x, y, parts[i].type, t); if (elements[t].ChangeType) (*(elements[t].ChangeType))(this, i, x, y, parts[i].type, t); if (parts[i].type > 0 && parts[i].type < PT_NUM && elementCount[parts[i].type]) elementCount[parts[i].type]--; elementCount[t]++; parts[i].type = t; if (elements[t].Properties & TYPE_ENERGY) { photons[y][x] = PMAP(i, t); if (ID(pmap[y][x]) == i) pmap[y][x] = 0; } else { pmap[y][x] = PMAP(i, t); if (ID(photons[y][x]) == i) photons[y][x] = 0; } return false; } //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. //tv = Type (PMAPBITS bits) + Var (32-PMAPBITS bits), var is usually 0 int Simulation::create_part(int p, int x, int y, int t, int v) { int i, oldType = PT_NONE; if (x<0 || y<0 || x>=XRES || y>=YRES || t<=0 || t>=PT_NUM || !elements[t].Enabled) return -1; if (t == PT_SPRK && !(p == -2 && elements[TYP(pmap[y][x])].CtypeDraw)) { int type = TYP(pmap[y][x]); int index = ID(pmap[y][x]); if(type == PT_WIRE) { parts[index].ctype = PT_DUST; return index; } if (!(type == PT_INST || (elements[type].Properties&PROP_CONDUCTS)) || parts[index].life!=0) return -1; if (p == -2 && type == PT_INST) { FloodINST(x, y); return index; } parts[index].type = PT_SPRK; parts[index].life = 4; parts[index].ctype = type; pmap[y][x] = (pmap[y][x]&~PMAPMASK) | PT_SPRK; 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)) parts[index].temp = parts[index].temp+10.0f; return index; } if (p == -2) { if (pmap[y][x]) { int drawOn = TYP(pmap[y][x]); if (elements[drawOn].CtypeDraw) elements[drawOn].CtypeDraw(this, ID(pmap[y][x]), t, v); return -1; } else if (IsWallBlocking(x, y, t)) return -1; else if (bmap[y/CELL][x/CELL]==WL_DETECT && elements[t].Properties & TYPE_SOLID) return -1; else if (photons[y][x] && (elements[t].Properties & TYPE_ENERGY)) return -1; } if (elements[t].CreateAllowed) { if (!(*(elements[t].CreateAllowed))(this, p, x, y, t)) return -1; } if (p == -1)//creating from anything but brush { // If there is a particle, only allow creation if the new particle can occupy the same space as the existing particle // If there isn't a particle but there is a wall, check whether the new particle is allowed to be in it // (not "!=2" for wall check because eval_move returns 1 for moving into empty space) // If there's no particle and no wall, assume creation is allowed if (pmap[y][x] ? (eval_move(t, x, y, NULL) != 2) : (bmap[y/CELL][x/CELL] && eval_move(t, x, y, NULL) == 0)) { return -1; } if (pfree == -1) return -1; i = pfree; pfree = parts[i].life; } else if (p == -2)//creating from brush { if (pfree == -1) return -1; i = pfree; pfree = parts[i].life; } else if (p == -3)//skip pmap checks, e.g. for sing explosion { if (pfree == -1) return -1; i = pfree; pfree = parts[i].life; } else { int oldX = (int)(parts[p].x + 0.5f); int oldY = (int)(parts[p].y + 0.5f); if (ID(pmap[oldY][oldX]) == p) pmap[oldY][oldX] = 0; if (ID(photons[oldY][oldX]) == p) photons[oldY][oldX] = 0; oldType = parts[p].type; if (elements[oldType].ChangeType) (*(elements[oldType].ChangeType))(this, p, oldX, oldY, oldType, t); if (oldType) elementCount[oldType]--; i = p; } if (i>parts_lastActiveIndex) parts_lastActiveIndex = i; parts[i] = elements[t].DefaultProperties; parts[i].type = t; parts[i].x = (float)x; parts[i].y = (float)y; //and finally set the pmap/photon maps to the newly created particle if (elements[t].Properties & TYPE_ENERGY) photons[y][x] = PMAP(i, t); else if (t!=PT_STKM && t!=PT_STKM2 && t!=PT_FIGH) pmap[y][x] = PMAP(i, t); //Fancy dust effects for powder types if((elements[t].Properties & TYPE_PART) && pretty_powder) { int colr, colg, colb; colr = PIXR(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15); colg = PIXG(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15); colb = PIXB(elements[t].Colour) + sandcolour * 1.3 + RNG::Ref().between(-20, 20) + RNG::Ref().between(-15, 15); colr = colr>255 ? 255 : (colr<0 ? 0 : colr); colg = colg>255 ? 255 : (colg<0 ? 0 : colg); colb = colb>255 ? 255 : (colb<0 ? 0 : colb); parts[i].dcolour = (RNG::Ref().between(0, 149)<<24) | (colr<<16) | (colg<<8) | colb; } // Set non-static properties (such as randomly generated ones) if (elements[t].Create) (*(elements[t].Create))(this, i, x, y, t, v); if (elements[t].ChangeType) (*(elements[t].ChangeType))(this, i, x, y, oldType, t); elementCount[t]++; return i; } void Simulation::GetGravityField(int x, int y, float particleGrav, float newtonGrav, float & pGravX, float & pGravY) { pGravX = newtonGrav*gravx[(y/CELL)*(XRES/CELL)+(x/CELL)]; pGravY = newtonGrav*gravy[(y/CELL)*(XRES/CELL)+(x/CELL)]; switch (gravityMode) { default: case 0: //normal, vertical gravity pGravY += particleGrav; break; case 1: //no gravity break; case 2: //radial gravity if (x-XCNTR != 0 || y-YCNTR != 0) { float pGravMult = particleGrav/sqrtf((x-XCNTR)*(x-XCNTR) + (y-YCNTR)*(y-YCNTR)); pGravX -= pGravMult * (float)(x - XCNTR); pGravY -= pGravMult * (float)(y - YCNTR); } } } void Simulation::create_gain_photon(int pp)//photons from PHOT going through GLOW { float xx, yy; int i, lr, temp_bin, nx, ny; if (pfree == -1) return; i = pfree; lr = RNG::Ref().between(0, 1); if (lr) { xx = parts[pp].x - 0.3*parts[pp].vy; yy = parts[pp].y + 0.3*parts[pp].vx; } else { xx = parts[pp].x + 0.3*parts[pp].vy; yy = parts[pp].y - 0.3*parts[pp].vx; } nx = (int)(xx + 0.5f); ny = (int)(yy + 0.5f); if (nx<0 || ny<0 || nx>=XRES || ny>=YRES) return; if (TYP(pmap[ny][nx]) != PT_GLOW) return; pfree = parts[i].life; if (i>parts_lastActiveIndex) parts_lastActiveIndex = i; parts[i].type = PT_PHOT; parts[i].life = 680; parts[i].x = xx; parts[i].y = yy; parts[i].vx = parts[pp].vx; parts[i].vy = parts[pp].vy; parts[i].temp = parts[ID(pmap[ny][nx])].temp; parts[i].tmp = 0; parts[i].pavg[0] = parts[i].pavg[1] = 0.0f; photons[ny][nx] = PMAP(i, PT_PHOT); temp_bin = (int)((parts[i].temp-273.0f)*0.25f); if (temp_bin < 0) temp_bin = 0; if (temp_bin > 25) temp_bin = 25; parts[i].ctype = 0x1F << temp_bin; } void Simulation::create_cherenkov_photon(int pp)//photons from NEUT going through GLAS { int i, lr, nx, ny; float r; if (pfree == -1) return; i = pfree; nx = (int)(parts[pp].x + 0.5f); ny = (int)(parts[pp].y + 0.5f); if (TYP(pmap[ny][nx]) != PT_GLAS && TYP(pmap[ny][nx]) != PT_BGLA) return; if (hypotf(parts[pp].vx, parts[pp].vy) < 1.44f) return; pfree = parts[i].life; if (i>parts_lastActiveIndex) parts_lastActiveIndex = i; lr = RNG::Ref().between(0, 1); parts[i].type = PT_PHOT; parts[i].ctype = 0x00000F80; parts[i].life = 680; parts[i].x = parts[pp].x; parts[i].y = parts[pp].y; parts[i].temp = parts[ID(pmap[ny][nx])].temp; parts[i].tmp = 0; parts[i].pavg[0] = parts[i].pavg[1] = 0.0f; photons[ny][nx] = PMAP(i, PT_PHOT); if (lr) { parts[i].vx = parts[pp].vx - 2.5f*parts[pp].vy; parts[i].vy = parts[pp].vy + 2.5f*parts[pp].vx; } else { parts[i].vx = parts[pp].vx + 2.5f*parts[pp].vy; parts[i].vy = parts[pp].vy - 2.5f*parts[pp].vx; } /* photons have speed of light. no discussion. */ r = 1.269 / hypotf(parts[i].vx, parts[i].vy); parts[i].vx *= r; parts[i].vy *= r; } void Simulation::delete_part(int x, int y)//calls kill_part with the particle located at x,y { unsigned i; if (x<0 || y<0 || x>=XRES || y>=YRES) return; if (photons[y][x]) { i = photons[y][x]; } else { i = pmap[y][x]; } if (!i) return; kill_part(ID(i)); } void Simulation::UpdateParticles(int start, int end) { int i, j, x, y, t, nx, ny, r, surround_space, s, rt, nt; float mv, dx, dy, nrx, nry, dp, ctemph, ctempl, gravtot; int fin_x, fin_y, clear_x, clear_y, stagnant; float fin_xf, fin_yf, clear_xf, clear_yf; float nn, ct1, ct2, swappage; float pt = R_TEMP; float c_heat = 0.0f; int h_count = 0; int surround[8]; int surround_hconduct[8]; float pGravX, pGravY, pGravD; bool transitionOccurred; //the main particle loop function, goes over all particles. for (i = start; i <= end && i <= parts_lastActiveIndex; i++) if (parts[i].type) { t = parts[i].type; x = (int)(parts[i].x+0.5f); y = (int)(parts[i].y+0.5f); //this kills any particle out of the screen, or in a wall where it isn't supposed to go if (x=XRES-CELL || y>=YRES-CELL || (bmap[y/CELL][x/CELL] && (bmap[y/CELL][x/CELL]==WL_WALL || bmap[y/CELL][x/CELL]==WL_WALLELEC || bmap[y/CELL][x/CELL]==WL_ALLOWAIR || (bmap[y/CELL][x/CELL]==WL_DESTROYALL) || (bmap[y/CELL][x/CELL]==WL_ALLOWLIQUID && !(elements[t].Properties&TYPE_LIQUID)) || (bmap[y/CELL][x/CELL]==WL_ALLOWPOWDER && !(elements[t].Properties&TYPE_PART)) || (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) || (bmap[y/CELL][x/CELL]==WL_ALLOWENERGY && !(elements[t].Properties&TYPE_ENERGY)) || (bmap[y/CELL][x/CELL]==WL_EWALL && !emap[y/CELL][x/CELL])) && (t!=PT_STKM) && (t!=PT_STKM2) && (t!=PT_FIGH))) { kill_part(i); continue; } // Make sure that STASIS'd particles don't tick. if (bmap[y/CELL][x/CELL] == WL_STASIS && emap[y/CELL][x/CELL]<8) { continue; } if (bmap[y/CELL][x/CELL]==WL_DETECT && emap[y/CELL][x/CELL]<8) set_emap(x/CELL, y/CELL); //adding to velocity from the particle's velocity vx[y/CELL][x/CELL] = vx[y/CELL][x/CELL]*elements[t].AirLoss + elements[t].AirDrag*parts[i].vx; vy[y/CELL][x/CELL] = vy[y/CELL][x/CELL]*elements[t].AirLoss + elements[t].AirDrag*parts[i].vy; if (elements[t].HotAir) { if (t==PT_GAS||t==PT_NBLE) { if (pv[y/CELL][x/CELL]<3.5f) pv[y/CELL][x/CELL] += elements[t].HotAir*(3.5f-pv[y/CELL][x/CELL]); if (y+CELL= 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 r = pmap[y-2][x]; if (!(!r || parts[i].type != TYP(r))) { if (parts[i].temp>parts[ID(r)].temp) { swappage = parts[i].temp; parts[i].temp = parts[ID(r)].temp; parts[ID(r)].temp = swappage; } } } //heat transfer code h_count = 0; #ifdef REALISTIC if (t&&(t!=PT_HSWC||parts[i].life==10)&&(elements[t].HeatConduct*gel_scale)) #else if (t && (t!=PT_HSWC||parts[i].life==10) && RNG::Ref().chance(elements[t].HeatConduct*gel_scale, 250)) #endif { if (aheat_enable && !(elements[t].Properties&PROP_NOAMBHEAT)) { #ifdef REALISTIC 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; float c_Cm = 96.645/elements[t].HeatConduct*gel_scale*fabs(elements[t].Weight) + 100*(pv[y/CELL][x/CELL]+273.15f)/256; pt = c_heat/c_Cm; pt = restrict_flt(pt, -MAX_TEMP+MIN_TEMP, MAX_TEMP-MIN_TEMP); parts[i].temp = pt; //Pressure increase from heat (temporary) pv[y/CELL][x/CELL] += (pt-hv[y/CELL][x/CELL])*0.004; hv[y/CELL][x/CELL] = pt; #else c_heat = (hv[y/CELL][x/CELL]-parts[i].temp)*0.04; c_heat = restrict_flt(c_heat, -MAX_TEMP+MIN_TEMP, MAX_TEMP-MIN_TEMP); parts[i].temp += c_heat; hv[y/CELL][x/CELL] -= c_heat; #endif } c_heat = 0.0f; #ifdef REALISTIC float c_Cm = 0.0f; #endif for (j=0; j<8; j++) { surround_hconduct[j] = i; r = surround[j]; if (!r) continue; rt = TYP(r); if (rt && elements[rt].HeatConduct && (rt!=PT_HSWC||parts[ID(r)].life==10) && (t!=PT_FILT||(rt!=PT_BRAY&&rt!=PT_BIZR&&rt!=PT_BIZRG)) && (rt!=PT_FILT||(t!=PT_BRAY&&t!=PT_PHOT&&t!=PT_BIZR&&t!=PT_BIZRG)) && (t!=PT_ELEC||rt!=PT_DEUT) && (t!=PT_DEUT||rt!=PT_ELEC) && (t!=PT_HSWC || rt!=PT_FILT || parts[i].tmp != 1) && (t!=PT_FILT || rt!=PT_HSWC || parts[ID(r)].tmp != 1)) { surround_hconduct[j] = ID(r); #ifdef REALISTIC if (rt==PT_GEL) gel_scale = parts[ID(r)].tmp*2.55f; else gel_scale = 1.0f; c_heat += parts[ID(r)].temp*96.645/elements[rt].HeatConduct*gel_scale*fabs(elements[rt].Weight); c_Cm += 96.645/elements[rt].HeatConduct*gel_scale*fabs(elements[rt].Weight); #else c_heat += parts[ID(r)].temp; #endif h_count++; } } #ifdef REALISTIC if (t==PT_GEL) gel_scale = parts[i].tmp*2.55f; else gel_scale = 1.0f; if (t == PT_PHOT) pt = (c_heat+parts[i].temp*96.645)/(c_Cm+96.645); else 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)); 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); parts[i].temp = restrict_flt(pt, MIN_TEMP, MAX_TEMP); #else pt = (c_heat+parts[i].temp)/(h_count+1); pt = parts[i].temp = restrict_flt(pt, MIN_TEMP, MAX_TEMP); for (j=0; j<8; j++) { parts[surround_hconduct[j]].temp = pt; } #endif ctemph = ctempl = pt; // change boiling point with pressure if (((elements[t].Properties&TYPE_LIQUID) && IsValidElement(elements[t].HighTemperatureTransition) && (elements[elements[t].HighTemperatureTransition].Properties&TYPE_GAS)) || t==PT_LNTG || t==PT_SLTW) ctemph -= 2.0f*pv[y/CELL][x/CELL]; else if (((elements[t].Properties&TYPE_GAS) && IsValidElement(elements[t].LowTemperatureTransition) && (elements[elements[t].LowTemperatureTransition].Properties&TYPE_LIQUID)) || t==PT_WTRV) ctempl -= 2.0f*pv[y/CELL][x/CELL]; s = 1; //A fix for ice with ctype = 0 if ((t==PT_ICEI || t==PT_SNOW) && (!parts[i].ctype || !IsValidElement(parts[i].ctype) || parts[i].ctype==PT_ICEI || parts[i].ctype==PT_SNOW)) parts[i].ctype = PT_WATR; if (elements[t].HighTemperatureTransition>-1 && ctemph>=elements[t].HighTemperature) { // particle type change due to high temperature #ifdef REALISTIC float dbt = ctempl - pt; if (elements[t].HighTemperatureTransition != PT_NUM) { if (platent[t] <= (c_heat - (elements[t].HighTemperature - dbt)*c_Cm)) { pt = (c_heat - platent[t])/c_Cm; t = elements[t].HighTemperatureTransition; } else { parts[i].temp = restrict_flt(elements[t].HighTemperature - dbt, MIN_TEMP, MAX_TEMP); s = 0; } } #else if (elements[t].HighTemperatureTransition != PT_NUM) t = elements[t].HighTemperatureTransition; #endif else if (t == PT_ICEI || t == PT_SNOW) { if (parts[i].ctype > 0 && parts[i].ctype < PT_NUM && parts[i].ctype != t) { if (elements[parts[i].ctype].LowTemperatureTransition==PT_ICEI || elements[parts[i].ctype].LowTemperatureTransition==PT_SNOW) { if (pt= elements[t].HighTemperature) t = PT_LAVA; else s = 0; } else if (t == PT_CRMC) { 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); if (ctemph < pres+elements[PT_CRMC].HighTemperature) s = 0; else t = PT_LAVA; } else s = 0; } else if (elements[t].LowTemperatureTransition > -1 && ctempl= (c_heat - (elements[t].LowTemperature - dbt)*c_Cm)) { pt = (c_heat + platent[elements[t].LowTemperatureTransition])/c_Cm; t = elements[t].LowTemperatureTransition; } else { parts[i].temp = restrict_flt(elements[t].LowTemperature - dbt, MIN_TEMP, MAX_TEMP); s = 0; } } #else if (elements[t].LowTemperatureTransition != PT_NUM) t = elements[t].LowTemperatureTransition; #endif else if (t == PT_WTRV) { if (pt < 273.0f) t = PT_RIME; else t = PT_DSTW; } else if (t == PT_LAVA) { if (parts[i].ctype>0 && parts[i].ctype=elements[PT_BMTL].HighTemperature) s = 0; else if ((parts[i].ctype==PT_VIBR || parts[i].ctype==PT_BVBR) && pt>=273.15f) s = 0; else if (parts[i].ctype==PT_TUNG) { // 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 // However, the threshold is stored in HighTemperature to allow it to be changed from Lua if (pt>=elements[parts[i].ctype].HighTemperature) s = 0; } else if (parts[i].ctype == PT_CRMC) { 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); if (ctemph >= pres+elements[PT_CRMC].HighTemperature) s = 0; } else if (elements[parts[i].ctype].HighTemperatureTransition == PT_LAVA || parts[i].ctype == PT_HEAC) { if (pt >= elements[parts[i].ctype].HighTemperature) s = 0; } else if (pt>=973.0f) s = 0; // freezing point for lava with any other (not listed in ptransitions as turning into lava) ctype if (s) { t = parts[i].ctype; parts[i].ctype = PT_NONE; if (t == PT_THRM) { parts[i].tmp = 0; t = PT_BMTL; } if (t == PT_PLUT) { parts[i].tmp = 0; t = PT_LAVA; } } } else if (pt<973.0f) t = PT_STNE; else s = 0; } else s = 0; } else s = 0; #ifdef REALISTIC pt = restrict_flt(pt, MIN_TEMP, MAX_TEMP); for (j=0; j<8; j++) { parts[surround_hconduct[j]].temp = pt; } #endif if (s) // particle type change occurred { if (t==PT_ICEI || t==PT_LAVA || t==PT_SNOW) parts[i].ctype = parts[i].type; if (!(t==PT_ICEI && parts[i].ctype==PT_FRZW)) parts[i].life = 0; if (t == PT_FIRE) { //hackish, if tmp isn't 0 the FIRE might turn into DSTW later //idealy transitions should use create_part(i) but some elements rely on properties staying constant //and I don't feel like checking each one right now parts[i].tmp = 0; } if ((elements[t].Properties&TYPE_GAS) && !(elements[parts[i].type].Properties&TYPE_GAS)) pv[y/CELL][x/CELL] += 0.50f; if (t == PT_NONE) { kill_part(i); goto killed; } // part_change_type could refuse to change the type and kill the particle // for example, changing type to STKM but one already exists // we need to account for that to not cause simulation corruption issues if (part_change_type(i,x,y,t)) goto killed; if (t==PT_FIRE || t==PT_PLSM || t==PT_CFLM) parts[i].life = RNG::Ref().between(120, 169); if (t == PT_LAVA) { if (parts[i].ctype == PT_BRMT) parts[i].ctype = PT_BMTL; else if (parts[i].ctype == PT_SAND) parts[i].ctype = PT_GLAS; else if (parts[i].ctype == PT_BGLA) parts[i].ctype = PT_GLAS; else if (parts[i].ctype == PT_PQRT) parts[i].ctype = PT_QRTZ; parts[i].life = RNG::Ref().between(240, 359); } transitionOccurred = true; } pt = parts[i].temp = restrict_flt(parts[i].temp, MIN_TEMP, MAX_TEMP); if (t == PT_LAVA) { parts[i].life = restrict_flt((parts[i].temp-700)/7, 0.0f, 400.0f); if (parts[i].ctype==PT_THRM&&parts[i].tmp>0) { parts[i].tmp--; parts[i].temp = 3500; } if (parts[i].ctype==PT_PLUT&&parts[i].tmp>0) { parts[i].tmp--; parts[i].temp = MAX_TEMP; } } } else { if (!(air->bmap_blockairh[y/CELL][x/CELL]&0x8)) air->bmap_blockairh[y/CELL][x/CELL]++; parts[i].temp = restrict_flt(parts[i].temp, MIN_TEMP, MAX_TEMP); } } if (t==PT_LIFE) { parts[i].temp = restrict_flt(parts[i].temp-50.0f, MIN_TEMP, MAX_TEMP); } if (t==PT_WIRE) { //wire_placed = 1; } //spark updates from walls if ((elements[t].Properties&PROP_CONDUCTS) || t==PT_SPRK) { nx = x % CELL; if (nx == 0) nx = x/CELL - 1; else if (nx == CELL-1) nx = x/CELL + 1; else nx = x/CELL; ny = y % CELL; if (ny == 0) ny = y/CELL - 1; else if (ny == CELL-1) ny = y/CELL + 1; else ny = y/CELL; if (nx>=0 && ny>=0 && nx2.5f) { parts[i].life = RNG::Ref().between(180, 259); parts[i].temp = restrict_flt(elements[PT_FIRE].DefaultProperties.temp + (elements[t].Flammable/2), MIN_TEMP, MAX_TEMP); t = PT_FIRE; part_change_type(i,x,y,t); pv[y/CELL][x/CELL] += 0.25f * CFDS; } s = 1; gravtot = fabs(gravy[(y/CELL)*(XRES/CELL)+(x/CELL)])+fabs(gravx[(y/CELL)*(XRES/CELL)+(x/CELL)]); if (elements[t].HighPressureTransition>-1 && pv[y/CELL][x/CELL]>elements[t].HighPressure) { // particle type change due to high pressure if (elements[t].HighPressureTransition!=PT_NUM) t = elements[t].HighPressureTransition; else if (t==PT_BMTL) { if (pv[y/CELL][x/CELL]>2.5f) t = PT_BRMT; else if (pv[y/CELL][x/CELL]>1.0f && parts[i].tmp==1) t = PT_BRMT; else s = 0; } else s = 0; } else if (elements[t].LowPressureTransition>-1 && pv[y/CELL][x/CELL]-1 && gravtot>(elements[t].HighPressure/4.0f)) { // particle type change due to high gravity if (elements[t].HighPressureTransition!=PT_NUM) t = elements[t].HighPressureTransition; else if (t==PT_BMTL) { if (gravtot>0.625f) t = PT_BRMT; else if (gravtot>0.25f && parts[i].tmp==1) t = PT_BRMT; else s = 0; } else s = 0; } else s = 0; // particle type change occurred if (s) { if (t == PT_NONE) { kill_part(i); goto killed; } parts[i].life = 0; // part_change_type could refuse to change the type and kill the particle // for example, changing type to STKM but one already exists // we need to account for that to not cause simulation corruption issues if (part_change_type(i,x,y,t)) goto killed; if (t == PT_FIRE) parts[i].life = RNG::Ref().between(120, 169); transitionOccurred = true; } //call the particle update function, if there is one #if !defined(RENDERER) && defined(LUACONSOLE) if (lua_el_mode[parts[i].type] == 3) { if (luacon_elementReplacement(this, i, x, y, surround_space, nt, parts, pmap) || t != parts[i].type) continue; // Need to update variables, in case they've been changed by Lua x = (int)(parts[i].x+0.5f); y = (int)(parts[i].y+0.5f); } if (elements[t].Update && lua_el_mode[t] != 2) #else if (elements[t].Update) #endif { if ((*(elements[t].Update))(this, i, x, y, surround_space, nt, parts, pmap)) continue; else if (t==PT_WARP) { // Warp does some movement in its update func, update variables to avoid incorrect data in pmap x = (int)(parts[i].x+0.5f); y = (int)(parts[i].y+0.5f); } } #if !defined(RENDERER) && defined(LUACONSOLE) if (lua_el_mode[parts[i].type] && lua_el_mode[parts[i].type] != 3) { if (luacon_elementReplacement(this, i, x, y, surround_space, nt, parts, pmap) || t != parts[i].type) continue; // Need to update variables, in case they've been changed by Lua x = (int)(parts[i].x+0.5f); y = (int)(parts[i].y+0.5f); } #endif if(legacy_enable)//if heat sim is off Element::legacyUpdate(this, i,x,y,surround_space,nt, parts, pmap); killed: if (parts[i].type == PT_NONE)//if its dead, skip to next particle continue; if (transitionOccurred) continue; if (!parts[i].vx&&!parts[i].vy)//if its not moving, skip to next particle, movement code it next continue; mv = fmaxf(fabsf(parts[i].vx), fabsf(parts[i].vy)); if (mv < ISTP) { clear_x = x; clear_y = y; clear_xf = parts[i].x; clear_yf = parts[i].y; fin_xf = clear_xf + parts[i].vx; fin_yf = clear_yf + parts[i].vy; fin_x = (int)(fin_xf+0.5f); fin_y = (int)(fin_yf+0.5f); } else { if (mv > SIM_MAXVELOCITY) { parts[i].vx *= SIM_MAXVELOCITY/mv; parts[i].vy *= SIM_MAXVELOCITY/mv; mv = SIM_MAXVELOCITY; } // interpolate to see if there is anything in the way dx = parts[i].vx*ISTP/mv; dy = parts[i].vy*ISTP/mv; fin_xf = parts[i].x; fin_yf = parts[i].y; fin_x = (int)(fin_xf+0.5f); fin_y = (int)(fin_yf+0.5f); bool closedEholeStart = this->InBounds(fin_x, fin_y) && (bmap[fin_y/CELL][fin_x/CELL] == WL_EHOLE && !emap[fin_y/CELL][fin_x/CELL]); while (1) { mv -= ISTP; fin_xf += dx; fin_yf += dy; fin_x = (int)(fin_xf+0.5f); fin_y = (int)(fin_yf+0.5f); if (edgeMode == 2) { bool x_ok = (fin_xf >= CELL-.5f && fin_xf < XRES-CELL-.5f); bool y_ok = (fin_yf >= CELL-.5f && fin_yf < YRES-CELL-.5f); if (!x_ok) fin_xf = remainder_p(fin_xf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f; if (!y_ok) fin_yf = remainder_p(fin_yf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f; fin_x = (int)(fin_xf+0.5f); fin_y = (int)(fin_yf+0.5f); } if (mv <= 0.0f) { // nothing found fin_xf = parts[i].x + parts[i].vx; fin_yf = parts[i].y + parts[i].vy; if (edgeMode == 2) { bool x_ok = (fin_xf >= CELL-.5f && fin_xf < XRES-CELL-.5f); bool y_ok = (fin_yf >= CELL-.5f && fin_yf < YRES-CELL-.5f); if (!x_ok) fin_xf = remainder_p(fin_xf-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f; if (!y_ok) fin_yf = remainder_p(fin_yf-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f; } fin_x = (int)(fin_xf+0.5f); fin_y = (int)(fin_yf+0.5f); clear_xf = fin_xf-dx; clear_yf = fin_yf-dy; clear_x = (int)(clear_xf+0.5f); clear_y = (int)(clear_yf+0.5f); break; } //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) //also photons are still blocked (slowed down) by any particle (even ones it can move through), and absorb wall also blocks particles int eval = eval_move(t, fin_x, fin_y, NULL); 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])) { // found an obstacle clear_xf = fin_xf-dx; clear_yf = fin_yf-dy; clear_x = (int)(clear_xf+0.5f); clear_y = (int)(clear_yf+0.5f); break; } if (bmap[fin_y/CELL][fin_x/CELL]==WL_DETECT && emap[fin_y/CELL][fin_x/CELL]<8) set_emap(fin_x/CELL, fin_y/CELL); } } stagnant = parts[i].flags & FLAG_STAGNANT; parts[i].flags &= ~FLAG_STAGNANT; if (t==PT_STKM || t==PT_STKM2 || t==PT_FIGH) { //head movement, let head pass through anything parts[i].x += parts[i].vx; parts[i].y += parts[i].vy; int nx = (int)((float)parts[i].x+0.5f); int ny = (int)((float)parts[i].y+0.5f); if (edgeMode == 2) { bool x_ok = (nx >= CELL && nx < XRES-CELL); bool y_ok = (ny >= CELL && ny < YRES-CELL); int oldnx = nx, oldny = ny; if (!x_ok) { parts[i].x = remainder_p(parts[i].x-CELL+.5f, XRES-CELL*2.0f)+CELL-.5f; nx = (int)((float)parts[i].x+0.5f); } if (!y_ok) { parts[i].y = remainder_p(parts[i].y-CELL+.5f, YRES-CELL*2.0f)+CELL-.5f; ny = (int)((float)parts[i].y+0.5f); } 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 { //adjust stickmen legs playerst* stickman = NULL; int t = parts[i].type; if (t == PT_STKM) stickman = &player; else if (t == PT_STKM2) stickman = &player2; else if (t == PT_FIGH && parts[i].tmp >= 0 && parts[i].tmp < MAX_FIGHTERS) stickman = &fighters[parts[i].tmp]; if (stickman) for (int i = 0; i < 16; i+=2) { stickman->legs[i] += (nx-oldnx); stickman->legs[i+1] += (ny-oldny); stickman->accs[i/2] *= .95f; } parts[i].vy *= .95f; parts[i].vx *= .95f; } } if (ny!=y || nx!=x) { if (ID(pmap[y][x]) == i) pmap[y][x] = 0; else if (ID(photons[y][x]) == i) photons[y][x] = 0; if (nx=XRES-CELL || ny=YRES-CELL) { kill_part(i); continue; } if (elements[t].Properties & TYPE_ENERGY) photons[ny][nx] = PMAP(i, t); else if (t) pmap[ny][nx] = PMAP(i, t); } } else if (elements[t].Properties & TYPE_ENERGY) { if (t == PT_PHOT) { if (parts[i].flags&FLAG_SKIPMOVE) { parts[i].flags &= ~FLAG_SKIPMOVE; continue; } if (eval_move(PT_PHOT, fin_x, fin_y, NULL)) { int rt = TYP(pmap[fin_y][fin_x]); int lt = TYP(pmap[y][x]); int rt_glas = (rt == PT_GLAS) || (rt == PT_BGLA); int lt_glas = (lt == PT_GLAS) || (lt == PT_BGLA); if ((rt_glas && !lt_glas) || (lt_glas && !rt_glas)) { if (!get_normal_interp(REFRACT|t, parts[i].x, parts[i].y, parts[i].vx, parts[i].vy, &nrx, &nry)) { kill_part(i); continue; } r = get_wavelength_bin(&parts[i].ctype); if (r == -1 || !(parts[i].ctype&0x3FFFFFFF)) { kill_part(i); continue; } nn = GLASS_IOR - GLASS_DISP*(r-30)/30.0f; nn *= nn; nrx = -nrx; nry = -nry; if (rt_glas && !lt_glas) nn = 1.0f/nn; ct1 = parts[i].vx*nrx + parts[i].vy*nry; ct2 = 1.0f - (nn*nn)*(1.0f-(ct1*ct1)); if (ct2 < 0.0f) { // total internal reflection parts[i].vx -= 2.0f*ct1*nrx; parts[i].vy -= 2.0f*ct1*nry; fin_xf = parts[i].x; fin_yf = parts[i].y; fin_x = x; fin_y = y; } else { // refraction ct2 = sqrtf(ct2); ct2 = ct2 - nn*ct1; parts[i].vx = nn*parts[i].vx + ct2*nrx; parts[i].vy = nn*parts[i].vy + ct2*nry; } } } } if (stagnant)//FLAG_STAGNANT set, was reflected on previous frame { // cast coords as int then back to float for compatibility with existing saves if (!do_move(i, x, y, (float)fin_x, (float)fin_y) && parts[i].type) { kill_part(i); continue; } } else if (!do_move(i, x, y, fin_xf, fin_yf)) { if (parts[i].type == PT_NONE) continue; // reflection parts[i].flags |= FLAG_STAGNANT; if (t==PT_NEUT && RNG::Ref().chance(1, 10)) { kill_part(i); continue; } r = pmap[fin_y][fin_x]; if ((TYP(r)==PT_PIPE || TYP(r) == PT_PPIP) && !TYP(parts[ID(r)].ctype)) { parts[ID(r)].ctype = parts[i].type; parts[ID(r)].temp = parts[i].temp; parts[ID(r)].tmp2 = parts[i].life; parts[ID(r)].pavg[0] = parts[i].tmp; parts[ID(r)].pavg[1] = parts[i].ctype; kill_part(i); continue; } if (TYP(r)) parts[i].ctype &= elements[TYP(r)].PhotonReflectWavelengths; if (get_normal_interp(t, parts[i].x, parts[i].y, parts[i].vx, parts[i].vy, &nrx, &nry)) { if (TYP(r) == PT_CRMC) { float r = RNG::Ref().between(-50, 50) * 0.01f, rx, ry, anrx, anry; r = r * r * r; rx = cosf(r); ry = sinf(r); anrx = rx * nrx + ry * nry; anry = rx * nry - ry * nrx; dp = anrx*parts[i].vx + anry*parts[i].vy; parts[i].vx -= 2.0f*dp*anrx; parts[i].vy -= 2.0f*dp*anry; } else { dp = nrx*parts[i].vx + nry*parts[i].vy; parts[i].vx -= 2.0f*dp*nrx; parts[i].vy -= 2.0f*dp*nry; } // leave the actual movement until next frame so that reflection of fast particles and refraction happen correctly } else { if (t!=PT_NEUT) kill_part(i); continue; } if (!(parts[i].ctype&0x3FFFFFFF) && t == PT_PHOT) { kill_part(i); continue; } } } else if (elements[t].Falldown==0) { // gasses and solids (but not powders) if (!do_move(i, x, y, fin_xf, fin_yf)) { if (parts[i].type == PT_NONE) continue; // can't move there, so bounce off // TODO // TODO: Work out what previous TODO was for if (fin_x>x+ISTP) fin_x=x+ISTP; if (fin_xy+ISTP) fin_y=y+ISTP; if (fin_y0.01f || fabsf(parts[i].vy)>0.01f)) { // allow diagonal movement if target position is blocked // but no point trying this if particle is stuck in a block of identical particles dx = parts[i].vx - parts[i].vy*r; dy = parts[i].vy + parts[i].vx*r; if (fabsf(dy)>fabsf(dx)) mv = fabsf(dy); else mv = fabsf(dx); dx /= mv; dy /= mv; if (do_move(i, x, y, clear_xf+dx, clear_yf+dy)) { parts[i].vx *= elements[t].Collision; parts[i].vy *= elements[t].Collision; goto movedone; } swappage = dx; dx = dy*r; dy = -swappage*r; if (do_move(i, x, y, clear_xf+dx, clear_yf+dy)) { parts[i].vx *= elements[t].Collision; parts[i].vy *= elements[t].Collision; goto movedone; } } if (elements[t].Falldown>1 && !grav->IsEnabled() && gravityMode==0 && parts[i].vy>fabsf(parts[i].vx)) { s = 0; // stagnant is true if FLAG_STAGNANT was set for this particle in previous frame if (!stagnant || nt) //nt is if there is an something else besides the current particle type, around the particle rt = 30;//slight less water lag, although it changes how it moves a lot else rt = 10; if (t==PT_GEL) rt = parts[i].tmp*0.20f+5.0f; for (j=clear_x+r; j>=0 && j>=clear_x-rt && j0) r = 1; else r = -1; if (s==1) for (j=ny+r; j>=0 && j=ny-rt && j1 && fabsf(pGravX*parts[i].vx+pGravY*parts[i].vy)>fabsf(pGravY*parts[i].vx-pGravX*parts[i].vy)) { float nxf, nyf, prev_pGravX, prev_pGravY, ptGrav = elements[t].Gravity; s = 0; // stagnant is true if FLAG_STAGNANT was set for this particle in previous frame if (!stagnant || nt) //nt is if there is an something else besides the current particle type, around the particle rt = 30;//slight less water lag, although it changes how it moves a lot else rt = 10; // clear_xf, clear_yf is the last known position that the particle should almost certainly be able to move to nxf = clear_xf; nyf = clear_yf; nx = clear_x; ny = clear_y; // Look for spaces to move horizontally (perpendicular to gravity direction), keep going until a space is found or the number of positions examined = rt for (j=0;jfabsf(pGravX)) mv = fabsf(pGravY); else mv = fabsf(pGravX); if (mv<0.0001f) break; pGravX /= mv; pGravY /= mv; // Move 1 pixel perpendicularly to gravity // r is +1/-1, to try moving left or right at random if (j) { // Not quite the gravity direction // Gravity direction + last change in gravity direction // 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. nxf += r*(pGravY*2.0f-prev_pGravY); nyf += -r*(pGravX*2.0f-prev_pGravX); } else { nxf += r*pGravY; nyf += -r*pGravX; } prev_pGravX = pGravX; prev_pGravY = pGravY; // Check whether movement is allowed nx = (int)(nxf+0.5f); ny = (int)(nyf+0.5f); if (nx<0 || ny<0 || nx>=XRES || ny >=YRES) break; if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]) { s = do_move(i, x, y, nxf, nyf); if (s) { // Movement was successful nx = (int)(parts[i].x+0.5f); ny = (int)(parts[i].y+0.5f); break; } // 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. if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]!=WL_STREAM) break; } } if (s==1) { // The particle managed to move horizontally, now try to move vertically (parallel to gravity direction) // Keep going until the particle is blocked (by something that isn't the same element) or the number of positions examined = rt clear_x = nx; clear_y = ny; for (j=0;jfabsf(pGravX)) mv = fabsf(pGravY); else mv = fabsf(pGravX); if (mv<0.0001f) break; pGravX /= mv; pGravY /= mv; // Move 1 pixel in the direction of gravity nxf += pGravX; nyf += pGravY; nx = (int)(nxf+0.5f); ny = (int)(nyf+0.5f); if (nx<0 || ny<0 || nx>=XRES || ny>=YRES) break; // 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 if (TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]) { s = do_move(i, clear_x, clear_y, nxf, nyf); if (s || TYP(pmap[ny][nx])!=t || bmap[ny/CELL][nx/CELL]!=WL_STREAM) break; // found the edge of the liquid and movement into it succeeded, so stop moving down } } } else if (s==-1) {} // particle is out of bounds else if ((clear_x!=x||clear_y!=y) && do_move(i, x, y, clear_xf, clear_yf)) {} // try moving to the last clear position else parts[i].flags |= FLAG_STAGNANT; parts[i].vx *= elements[t].Collision; parts[i].vy *= elements[t].Collision; } else { // if interpolation was done, try moving to last clear position if ((clear_x!=x||clear_y!=y) && do_move(i, x, y, clear_xf, clear_yf)) {} else parts[i].flags |= FLAG_STAGNANT; parts[i].vx *= elements[t].Collision; parts[i].vy *= elements[t].Collision; } } } } movedone: continue; } //'f' was pressed (single frame) if (framerender) framerender--; } int Simulation::GetParticleType(ByteString type) { char * txt = (char*)type.c_str(); // alternative names for some elements if (!strcasecmp(txt, "C4")) return PT_PLEX; else if (!strcasecmp(txt, "C5")) return PT_C5; else if (!strcasecmp(txt, "NONE")) return PT_NONE; for (int i = 1; i < PT_NUM; i++) { if (!strcasecmp(txt, elements[i].Name.ToUtf8().c_str()) && elements[i].Name.size() && elements[i].Enabled) { return i; } } return -1; } void Simulation::RecalcFreeParticles(bool do_life_dec) { int x, y, t; int lastPartUsed = 0; int lastPartUnused = -1; memset(pmap, 0, sizeof(pmap)); memset(pmap_count, 0, sizeof(pmap_count)); memset(photons, 0, sizeof(photons)); NUM_PARTS = 0; //the particle loop that resets the pmap/photon maps every frame, to update them. for (int i = 0; i <= parts_lastActiveIndex; i++) { if (parts[i].type) { t = parts[i].type; x = (int)(parts[i].x+0.5f); y = (int)(parts[i].y+0.5f); bool inBounds = false; if (x>=0 && y>=0 && x=PT_NUM || !elements[t].Enabled) { kill_part(i); continue; } if (elementRecount) elementCount[t]++; unsigned int elem_properties = elements[t].Properties; if (parts[i].life>0 && (elem_properties&PROP_LIFE_DEC) && !(inBounds && bmap[y/CELL][x/CELL] == WL_STASIS && emap[y/CELL][x/CELL]<8)) { // automatically decrease life parts[i].life--; if (parts[i].life<=0 && (elem_properties&(PROP_LIFE_KILL_DEC|PROP_LIFE_KILL))) { // kill on change to no life kill_part(i); continue; } } 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)) { // kill if no life kill_part(i); continue; } } } else { if (lastPartUnused<0) pfree = i; else parts[lastPartUnused].life = i; lastPartUnused = i; } } if (lastPartUnused == -1) { if (parts_lastActiveIndex>=NPART-1) pfree = -1; else pfree = parts_lastActiveIndex+1; } else { if (parts_lastActiveIndex>=NPART-1) parts[lastPartUnused].life = -1; else parts[lastPartUnused].life = parts_lastActiveIndex+1; } parts_lastActiveIndex = lastPartUsed; if (elementRecount && (!sys_pause || framerender)) elementRecount = false; } void Simulation::SimulateGoL() { CGOL = 0; for (int i = 0; i <= parts_lastActiveIndex; ++i) { auto &part = parts[i]; if (part.type != PT_LIFE) { continue; } auto x = int(part.x + 0.5f); auto y = int(part.y + 0.5f); if (x < CELL || y < CELL || x >= XRES - CELL || y >= YRES - CELL) { continue; } unsigned int golnum = part.ctype; unsigned int ruleset = golnum; if (golnum < NGOL) { ruleset = builtinGol[golnum].ruleset; golnum += 1; } if (part.tmp2 == int((ruleset >> 17) & 0xF) + 1) { for (int yy = -1; yy <= 1; ++yy) { for (int xx = -1; xx <= 1; ++xx) { if (xx || yy) { // * Calculate address of the neighbourList, taking wraparound // into account. The fact that the GOL space is 2 CELL's worth // narrower in both dimensions than the simulation area makes // this a bit awkward. int ax = ((x + xx + XRES - 3 * CELL) % (XRES - 2 * CELL)) + CELL; int ay = ((y + yy + YRES - 3 * CELL) % (YRES - 2 * CELL)) + CELL; unsigned int (&neighbourList)[5] = gol[ay][ax]; // * Bump overall neighbour counter (bits 30..28) for the entire list. neighbourList[0] += 1U << 28; for (int l = 0; l < 5; ++l) { auto neighbourRuleset = neighbourList[l] & 0x001FFFFFU; if (neighbourRuleset == golnum) { // * Bump population counter (bits 23..21) of the // same kind of cell. neighbourList[l] += 1U << 21; break; } if (neighbourRuleset == 0) { // * Add the new kind of cell to the population. Both counters // have a bias of -1, so they're intentionally initialised // to 0 instead of 1 here. This is all so they can both // fit in 3 bits. neighbourList[l] = ((yy & 3) << 26) | ((xx & 3) << 24) | golnum; break; } // * If after 5 iterations the cell still hasn't contributed // to a list entry, it's surely a 6th kind of cell, meaning // there could be at most 3 of it in the neighbourhood, // as there are already 5 other kinds of cells present in // the list. This in turn means that it couldn't possibly // win the population ratio-based contest later on. } } } } } else { if (!(bmap[y / CELL][x / CELL] == WL_STASIS && emap[y / CELL][x / CELL] < 8)) { part.tmp2 -= 1; } } } for (int y = CELL; y < YRES - CELL; ++y) { for (int x = CELL; x < XRES - CELL; ++x) { int r = pmap[y][x]; if (r && TYP(r) != PT_LIFE) { continue; } unsigned int (&neighbourList)[5] = gol[y][x]; auto nl0 = neighbourList[0]; if (r || nl0) { // * Get overall neighbour count (bits 30..28). unsigned int neighbours = nl0 ? ((nl0 >> 28) & 7) + 1 : 0; if (!(bmap[y / CELL][x / CELL] == WL_STASIS && emap[y / CELL][x / CELL] < 8)) { if (r) { auto &part = parts[ID(r)]; unsigned int ruleset = part.ctype; if (ruleset < NGOL) { ruleset = builtinGol[ruleset].ruleset; } if (!((ruleset >> neighbours) & 1) && part.tmp2 == int(ruleset >> 17) + 1) { // * Start death sequence. part.tmp2 -= 1; } } else { unsigned int golnumToCreate = 0xFFFFFFFFU; unsigned int createFromEntry = 0U; unsigned int majority = neighbours / 2 + neighbours % 2; for (int l = 0; l < 5; ++l) { auto golnum = neighbourList[l] & 0x001FFFFFU; if (!golnum) { break; } auto ruleset = golnum; if (golnum - 1 < NGOL) { ruleset = builtinGol[golnum - 1].ruleset; golnum -= 1; } if ((ruleset >> (neighbours + 8)) & 1 && ((neighbourList[l] >> 21) & 7) + 1 >= majority && golnum < golnumToCreate) { golnumToCreate = golnum; createFromEntry = neighbourList[l]; } } if (golnumToCreate != 0xFFFFFFFFU) { // * 0x200000: No need to look for colours, they'll be set later anyway. int i = create_part(-1, x, y, PT_LIFE, golnumToCreate | 0x200000); int xx = (createFromEntry >> 24) & 3; int yy = (createFromEntry >> 26) & 3; if (xx == 3) xx = -1; if (yy == 3) yy = -1; int ax = ((x - xx + XRES - 3 * CELL) % (XRES - 2 * CELL)) + CELL; int ay = ((y - yy + YRES - 3 * CELL) % (YRES - 2 * CELL)) + CELL; auto &sample = parts[ID(pmap[ay][ax])]; parts[i].dcolour = sample.dcolour; parts[i].tmp = sample.tmp; } } } for (int l = 0; l < 5 && neighbourList[l]; ++l) { neighbourList[l] = 0; } } } } for (int y = CELL; y < YRES - CELL; ++y) { for (int x = CELL; x < XRES - CELL; ++x) { int r = pmap[y][x]; if (r && TYP(r) == PT_LIFE && parts[ID(r)].tmp2 <= 0) { kill_part(ID(r)); } } } } void Simulation::CheckStacking() { bool excessive_stacking_found = false; force_stacking_check = false; for (int y = 0; y < YRES; y++) { for (int x = 0; x < XRES; x++) { // Use a threshold, since some particle stacking can be normal (e.g. BIZR + FILT) // Setting pmap_count[y][x] > NPART means BHOL will form in that spot if (pmap_count[y][x]>5) { if (bmap[y/CELL][x/CELL]==WL_EHOLE) { // Allow more stacking in E-hole if (pmap_count[y][x]>1500) { pmap_count[y][x] = pmap_count[y][x] + NPART; excessive_stacking_found = 1; } } else if (pmap_count[y][x]>1500 || (unsigned int)RNG::Ref().between(0, 1599) <= (pmap_count[y][x]+100)) { pmap_count[y][x] = pmap_count[y][x] + NPART; excessive_stacking_found = true; } } } } if (excessive_stacking_found) { for (int i = 0; i <= parts_lastActiveIndex; i++) { if (parts[i].type) { int t = parts[i].type; int x = (int)(parts[i].x+0.5f); int y = (int)(parts[i].y+0.5f); if (x>=0 && y>=0 && x=NPART) { if (pmap_count[y][x]>NPART) { create_part(i, x, y, PT_NBHL); parts[i].temp = MAX_TEMP; parts[i].tmp = pmap_count[y][x]-NPART;//strength of grav field if (parts[i].tmp>51200) parts[i].tmp = 51200; pmap_count[y][x] = NPART; } else { kill_part(i); } } } } } } } //updates pmap, gol, and some other simulation stuff (but not particles) void Simulation::BeforeSim() { if (!sys_pause||framerender) { air->update_air(); if(aheat_enable) air->update_airh(); if(grav->IsEnabled()) { grav->gravity_update_async(); //Get updated buffer pointers for gravity gravx = grav->gravx; gravy = grav->gravy; gravp = grav->gravp; gravmap = grav->gravmap; } if(emp_decor>0) emp_decor -= emp_decor/25+2; if(emp_decor < 0) emp_decor = 0; etrd_count_valid = false; etrd_life0_count = 0; currentTick++; elementRecount |= !(currentTick%180); if (elementRecount) std::fill(elementCount, elementCount+PT_NUM, 0); } sandcolour = (int)(20.0f*sin((float)sandcolour_frame*(M_PI/180.0f))); sandcolour_frame = (sandcolour_frame+1)%360; if (gravWallChanged) { grav->gravity_mask(); gravWallChanged = false; } if (debug_currentParticle == 0) RecalcFreeParticles(true); if (!sys_pause || framerender) { // decrease wall conduction, make walls block air and ambient heat int x, y; for (y = 0; y < YRES/CELL; y++) { for (x = 0; x < XRES/CELL; x++) { if (emap[y][x]) emap[y][x] --; 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])); 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; } } // check for stacking and create BHOL if found if (force_stacking_check || RNG::Ref().chance(1, 10)) { CheckStacking(); } // LOVE and LOLZ element handling if (elementCount[PT_LOVE] > 0 || elementCount[PT_LOLZ] > 0) { int nx, nnx, ny, nny, r, rt; for (ny=0; nyYRES-7||nx>XRES-10)&&(parts[ID(r)].type==PT_LOVE||parts[ID(r)].type==PT_LOLZ)) kill_part(ID(r)); else if (parts[ID(r)].type==PT_LOVE) { Element_LOVE_love[nx/9][ny/9] = 1; } else if (parts[ID(r)].type==PT_LOLZ) { Element_LOLZ_lolz[nx/9][ny/9] = 1; } } } for (nx=9; nx<=XRES-18; nx++) { for (ny=9; ny<=YRES-7; ny++) { if (Element_LOVE_love[nx/9][ny/9]==1) { for ( nnx=0; nnx<9; nnx++) for ( nny=0; nny<9; nny++) { if (ny+nny>0&&ny+nny=0&&nx+nnx0&&ny+nny=0&&nx+nnx 0) { for (int nx = 0; nx < XRES; nx++) { for (int ny = 0; ny < YRES; ny++) { int r = pmap[ny][nx]; if (!r) continue; if(parts[ID(r)].type == PT_WIRE) parts[ID(r)].tmp = parts[ID(r)].ctype; } } } // update PPIP tmp? if (Element_PPIP_ppip_changed) { for (int i = 0; i <= parts_lastActiveIndex; i++) { if (parts[i].type==PT_PPIP) { parts[i].tmp |= (parts[i].tmp&0xE0000000)>>3; parts[i].tmp &= ~0xE0000000; } } Element_PPIP_ppip_changed = 0; } // Simulate GoL // GSPEED is frames per generation if (elementCount[PT_LIFE]>0 && ++CGOL>=GSPEED) { SimulateGoL(); } // wifi channel reseting if (ISWIRE > 0) { for (int q = 0; q < (int)(MAX_TEMP-73.15f)/100+2; q++) { wireless[q][0] = wireless[q][1]; wireless[q][1] = 0; } ISWIRE--; } // spawn STKM and STK2 if (!player.spwn && player.spawnID >= 0) create_part(-1, (int)parts[player.spawnID].x, (int)parts[player.spawnID].y, PT_STKM); if (!player2.spwn && player2.spawnID >= 0) create_part(-1, (int)parts[player2.spawnID].x, (int)parts[player2.spawnID].y, PT_STKM2); // particle update happens right after this function (called separately) } } void Simulation::AfterSim() { if (emp_trigger_count) { // pitiful attempt at trying to keep code relating to a given element in the same file void Element_EMP_Trigger(Simulation *sim, int triggerCount); Element_EMP_Trigger(this, emp_trigger_count); emp_trigger_count = 0; } } Simulation::~Simulation() { delete grav; delete air; } Simulation::Simulation(): replaceModeSelected(0), replaceModeFlags(0), debug_currentParticle(0), ISWIRE(0), force_stacking_check(false), emp_decor(0), emp_trigger_count(0), etrd_count_valid(false), etrd_life0_count(0), lightningRecreate(0), gravWallChanged(false), CGOL(0), GSPEED(1), edgeMode(0), gravityMode(0), legacy_enable(0), aheat_enable(0), water_equal_test(0), sys_pause(0), framerender(0), pretty_powder(0), sandcolour_frame(0), deco_space(0) { int tportal_rx[] = {-1, 0, 1, 1, 1, 0,-1,-1}; int tportal_ry[] = {-1,-1,-1, 0, 1, 1, 1, 0}; memcpy(portal_rx, tportal_rx, sizeof(tportal_rx)); memcpy(portal_ry, tportal_ry, sizeof(tportal_ry)); currentTick = 0; std::fill(elementCount, elementCount+PT_NUM, 0); elementRecount = true; //Create and attach gravity simulation grav = new Gravity(); //Give air sim references to our data grav->bmap = bmap; //Gravity sim gives us maps to use gravx = grav->gravx; gravy = grav->gravy; gravp = grav->gravp; gravmap = grav->gravmap; //Create and attach air simulation air = new Air(*this); //Give air sim references to our data air->bmap = bmap; air->emap = emap; air->fvx = fvx; air->fvy = fvy; //Air sim gives us maps to use vx = air->vx; vy = air->vy; pv = air->pv; hv = air->hv; msections = LoadMenus(); wtypes = LoadWalls(); platent = LoadLatent(); std::copy(GetElements().begin(), GetElements().end(), elements.begin()); tools = GetTools(); player.comm = 0; player2.comm = 0; init_can_move(); clear_sim(); grav->gravity_mask(); } const Simulation::CustomGOLData *Simulation::GetCustomGOLByRule(int rule) const { // * Binary search. customGol is already sorted, see SetCustomGOL. auto it = std::lower_bound(customGol.begin(), customGol.end(), rule, [](const CustomGOLData &item, int rule) { return item.rule < rule; }); if (it != customGol.end() && !(rule < it->rule)) { return &*it; } return nullptr; } void Simulation::SetCustomGOL(std::vector newCustomGol) { std::sort(newCustomGol.begin(), newCustomGol.end()); customGol = newCustomGol; } String Simulation::ElementResolve(int type, int ctype) { if (type == PT_LIFE) { if (ctype >= 0 && ctype < NGOL) { return builtinGol[ctype].name; } auto *cgol = GetCustomGOLByRule(ctype); if (cgol) { return cgol->nameString; } return SerialiseGOLRule(ctype); } else if (type >= 0 && type < PT_NUM) { return elements[type].Name; } return "Empty"; } String Simulation::BasicParticleInfo(Particle const &sample_part) { StringBuilder sampleInfo; int type = sample_part.type; int ctype = sample_part.ctype; int pavg1int = (int)sample_part.pavg[1]; if (type == PT_LAVA && ctype && IsValidElement(ctype)) { sampleInfo << "Molten " << ElementResolve(ctype, -1); } else if ((type == PT_PIPE || type == PT_PPIP) && ctype && IsValidElement(ctype)) { if (ctype == PT_LAVA && pavg1int && IsValidElement(pavg1int)) { sampleInfo << ElementResolve(type, -1) << " with molten " << ElementResolve(pavg1int, -1); } else { sampleInfo << ElementResolve(type, -1) << " with " << ElementResolve(ctype, pavg1int); } } else { sampleInfo << ElementResolve(type, ctype); } return sampleInfo.Build(); } bool Simulation::InBounds(int x, int y) { return (x>=0 && y>=0 && x=0 ? 0 : y); } float Simulation::remainder_p(float x, float y) { return std::fmod(x, y) + (x>=0 ? 0 : y); } constexpr size_t ce_log2(size_t n) { return ((n < 2) ? 1 : 1 + ce_log2(n / 2)); } static_assert(PMAPBITS <= 16, "PMAPBITS is too large"); // * This will technically fail in some cases where (XRES * YRES) << PMAPBITS would // fit in 31 bits but multiplication is evil and wraps around without you knowing it. // * Whoever runs into a problem with this (e.g. with XRES = 612, YRES = 384 and // PMAPBITS = 13) should just remove the check and take responsibility otherwise. static_assert(ce_log2(XRES) + ce_log2(YRES) + PMAPBITS <= 31, "not enough space in pmap");