diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Particle.c |
initial commit
Diffstat (limited to 'src/Particle.c')
-rw-r--r-- | src/Particle.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/src/Particle.c b/src/Particle.c new file mode 100644 index 0000000..977fbfa --- /dev/null +++ b/src/Particle.c @@ -0,0 +1,619 @@ +#include "Particle.h" +#include "Block.h" +#include "World.h" +#include "ExtMath.h" +#include "Lighting.h" +#include "Entity.h" +#include "TexturePack.h" +#include "Graphics.h" +#include "Funcs.h" +#include "Game.h" +#include "Event.h" + + +/*########################################################################################################################* +*------------------------------------------------------Particle base------------------------------------------------------* +*#########################################################################################################################*/ +static GfxResourceID particles_TexId, particles_VB; +#define PARTICLES_MAX 600 +static RNGState rnd; +static cc_bool hitTerrain; +typedef cc_bool (*CanPassThroughFunc)(BlockID b); + +void Particle_DoRender(const Vec2* size, const Vec3* pos, const TextureRec* rec, PackedCol col, struct VertexTextured* v) { + struct Matrix* view; + float sX, sY; + Vec3 centre; + float aX, aY, aZ, bX, bY, bZ; + + sX = size->x * 0.5f; sY = size->y * 0.5f; + centre = *pos; centre.y += sY; + view = &Gfx.View; + + aX = view->row1.x * sX; aY = view->row2.x * sX; aZ = view->row3.x * sX; /* right * size.x * 0.5f */ + bX = view->row1.y * sY; bY = view->row2.y * sY; bZ = view->row3.y * sY; /* up * size.y * 0.5f */ + + v->x = centre.x - aX - bX; v->y = centre.y - aY - bY; v->z = centre.z - aZ - bZ; v->Col = col; v->U = rec->u1; v->V = rec->v2; v++; + v->x = centre.x - aX + bX; v->y = centre.y - aY + bY; v->z = centre.z - aZ + bZ; v->Col = col; v->U = rec->u1; v->V = rec->v1; v++; + v->x = centre.x + aX + bX; v->y = centre.y + aY + bY; v->z = centre.z + aZ + bZ; v->Col = col; v->U = rec->u2; v->V = rec->v1; v++; + v->x = centre.x + aX - bX; v->y = centre.y + aY - bY; v->z = centre.z + aZ - bZ; v->Col = col; v->U = rec->u2; v->V = rec->v2; v++; +} + +static cc_bool CollidesHor(Vec3* nextPos, BlockID block) { + Vec3 horPos = Vec3_Create3((float)Math_Floor(nextPos->x), 0.0f, (float)Math_Floor(nextPos->z)); + Vec3 min, max; + Vec3_Add(&min, &Blocks.MinBB[block], &horPos); + Vec3_Add(&max, &Blocks.MaxBB[block], &horPos); + return nextPos->x >= min.x && nextPos->z >= min.z && nextPos->x < max.x && nextPos->z < max.z; +} + +static BlockID GetBlock(int x, int y, int z) { + if (World_Contains(x, y, z)) return World_GetBlock(x, y, z); + + if (y >= Env.EdgeHeight) return BLOCK_AIR; + if (y >= Env_SidesHeight) return Env.EdgeBlock; + return Env.SidesBlock; +} + +static cc_bool ClipY(struct Particle* p, int y, cc_bool topFace, CanPassThroughFunc canPassThrough) { + BlockID block; + Vec3 minBB, maxBB; + float collideY; + cc_bool collideVer; + + if (y < 0) { + p->nextPos.y = ENTITY_ADJUSTMENT; + p->lastPos.y = ENTITY_ADJUSTMENT; + + Vec3_Set(p->velocity, 0,0,0); + hitTerrain = true; + return false; + } + + block = GetBlock((int)p->nextPos.x, y, (int)p->nextPos.z); + if (canPassThrough(block)) return true; + minBB = Blocks.MinBB[block]; maxBB = Blocks.MaxBB[block]; + + collideY = y + (topFace ? maxBB.y : minBB.y); + collideVer = topFace ? (p->nextPos.y < collideY) : (p->nextPos.y > collideY); + + if (collideVer && CollidesHor(&p->nextPos, block)) { + float adjust = topFace ? ENTITY_ADJUSTMENT : -ENTITY_ADJUSTMENT; + p->lastPos.y = collideY + adjust; + p->nextPos.y = p->lastPos.y; + + Vec3_Set(p->velocity, 0,0,0); + hitTerrain = true; + return false; + } + return true; +} + +static cc_bool IntersectsBlock(struct Particle* p, CanPassThroughFunc canPassThrough) { + BlockID cur = GetBlock((int)p->nextPos.x, (int)p->nextPos.y, (int)p->nextPos.z); + float minY = Math_Floor(p->nextPos.y) + Blocks.MinBB[cur].y; + float maxY = Math_Floor(p->nextPos.y) + Blocks.MaxBB[cur].y; + + return !canPassThrough(cur) && p->nextPos.y >= minY && p->nextPos.y < maxY && CollidesHor(&p->nextPos, cur); +} + +static cc_bool PhysicsTick(struct Particle* p, float gravity, CanPassThroughFunc canPassThrough, float delta) { + Vec3 velocity; + int y, begY, endY; + + p->lastPos = p->nextPos; + if (IntersectsBlock(p, canPassThrough)) return true; + + p->velocity.y -= gravity * delta; + begY = Math_Floor(p->nextPos.y); + + Vec3_Mul1(&velocity, &p->velocity, delta * 3.0f); + Vec3_Add(&p->nextPos, &p->nextPos, &velocity); + endY = Math_Floor(p->nextPos.y); + + if (p->velocity.y > 0.0f) { + /* don't test block we are already in */ + for (y = begY + 1; y <= endY && ClipY(p, y, false, canPassThrough); y++) {} + } else { + for (y = begY; y >= endY && ClipY(p, y, true, canPassThrough); y--) {} + } + + p->lifetime -= delta; + return p->lifetime < 0.0f; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Rain particle-----------------------------------------------------* +*#########################################################################################################################*/ +static struct Particle rain_Particles[PARTICLES_MAX]; +static int rain_count; +static TextureRec rain_rec = { 2.0f/128.0f, 14.0f/128.0f, 5.0f/128.0f, 16.0f/128.0f }; + +static cc_bool RainParticle_CanPass(BlockID block) { + cc_uint8 draw = Blocks.Draw[block]; + return draw == DRAW_GAS || draw == DRAW_SPRITE; +} + +static cc_bool RainParticle_Tick(struct Particle* p, float delta) { + hitTerrain = false; + return PhysicsTick(p, 3.5f, RainParticle_CanPass, delta) || hitTerrain; +} + +static void RainParticle_Render(struct Particle* p, float t, struct VertexTextured* vertices) { + Vec3 pos; + Vec2 size; + PackedCol col; + int x, y, z; + + Vec3_Lerp(&pos, &p->lastPos, &p->nextPos, t); + size.x = p->size * 0.015625f; size.y = size.x; + + x = Math_Floor(pos.x); y = Math_Floor(pos.y); z = Math_Floor(pos.z); + col = Lighting.Color(x, y, z); + Particle_DoRender(&size, &pos, &rain_rec, col, vertices); +} + +static void Rain_Render(float t) { + struct VertexTextured* data; + int i; + if (!rain_count) return; + + data = (struct VertexTextured*)Gfx_LockDynamicVb(particles_VB, + VERTEX_FORMAT_TEXTURED, rain_count * 4); + for (i = 0; i < rain_count; i++) { + RainParticle_Render(&rain_Particles[i], t, data); + data += 4; + } + + Gfx_BindTexture(particles_TexId); + Gfx_UnlockDynamicVb(particles_VB); + Gfx_DrawVb_IndexedTris(rain_count * 4); +} + +static void Rain_RemoveAt(int i) { + for (; i < rain_count - 1; i++) { + rain_Particles[i] = rain_Particles[i + 1]; + } + rain_count--; +} + +static void Rain_Tick(float delta) { + int i; + for (i = 0; i < rain_count; i++) { + if (RainParticle_Tick(&rain_Particles[i], delta)) { + Rain_RemoveAt(i); i--; + } + } +} + +void Particles_RainSnowEffect(float x, float y, float z) { + struct Particle* p; + int i, type; + + for (i = 0; i < 2; i++) { + if (rain_count == PARTICLES_MAX) Rain_RemoveAt(0); + p = &rain_Particles[rain_count++]; + + p->velocity.x = Random_Float(&rnd) * 0.8f - 0.4f; /* [-0.4, 0.4] */ + p->velocity.z = Random_Float(&rnd) * 0.8f - 0.4f; + p->velocity.y = Random_Float(&rnd) + 0.4f; + + p->lastPos.x = x + Random_Float(&rnd); /* [0.0, 1.0] */ + p->lastPos.y = y + Random_Float(&rnd) * 0.1f + 0.01f; + p->lastPos.z = z + Random_Float(&rnd); + + p->nextPos = p->lastPos; + p->lifetime = 40.0f; + + type = Random_Next(&rnd, 30); + p->size = type >= 28 ? 2 : (type >= 25 ? 4 : 3); + } +} + + +/*########################################################################################################################* +*------------------------------------------------------Terrain particle---------------------------------------------------* +*#########################################################################################################################*/ +struct TerrainParticle { + struct Particle base; + TextureRec rec; + TextureLoc texLoc; + BlockID block; +}; + +static struct TerrainParticle terrain_particles[PARTICLES_MAX]; +static int terrain_count; +static cc_uint16 terrain_1DCount[ATLAS1D_MAX_ATLASES]; +static cc_uint16 terrain_1DIndices[ATLAS1D_MAX_ATLASES]; + +static cc_bool TerrainParticle_CanPass(BlockID block) { + cc_uint8 draw = Blocks.Draw[block]; + return draw == DRAW_GAS || draw == DRAW_SPRITE || Blocks.IsLiquid[block]; +} + +static cc_bool TerrainParticle_Tick(struct TerrainParticle* p, float delta) { + return PhysicsTick(&p->base, Blocks.ParticleGravity[p->block], TerrainParticle_CanPass, delta); +} + +static void TerrainParticle_Render(struct TerrainParticle* p, float t, struct VertexTextured* vertices) { + PackedCol col = PACKEDCOL_WHITE; + Vec3 pos; + Vec2 size; + int x, y, z; + + Vec3_Lerp(&pos, &p->base.lastPos, &p->base.nextPos, t); + size.x = p->base.size * 0.015625f; size.y = size.x; + + if (!Blocks.Brightness[p->block]) { + x = Math_Floor(pos.x); y = Math_Floor(pos.y); z = Math_Floor(pos.z); + col = Lighting.Color_XSide(x, y, z); + } + + Block_Tint(col, p->block); + Particle_DoRender(&size, &pos, &p->rec, col, vertices); +} + +static void Terrain_Update1DCounts(void) { + int i, index; + + for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { + terrain_1DCount[i] = 0; + terrain_1DIndices[i] = 0; + } + for (i = 0; i < terrain_count; i++) { + index = Atlas1D_Index(terrain_particles[i].texLoc); + terrain_1DCount[index] += 4; + } + for (i = 1; i < Atlas1D.Count; i++) { + terrain_1DIndices[i] = terrain_1DIndices[i - 1] + terrain_1DCount[i - 1]; + } +} + +static void Terrain_Render(float t) { + struct VertexTextured* data; + struct VertexTextured* ptr; + int offset = 0; + int i, index; + if (!terrain_count) return; + + data = (struct VertexTextured*)Gfx_LockDynamicVb(particles_VB, + VERTEX_FORMAT_TEXTURED, terrain_count * 4); + Terrain_Update1DCounts(); + for (i = 0; i < terrain_count; i++) { + index = Atlas1D_Index(terrain_particles[i].texLoc); + ptr = data + terrain_1DIndices[index]; + + TerrainParticle_Render(&terrain_particles[i], t, ptr); + terrain_1DIndices[index] += 4; + } + + Gfx_UnlockDynamicVb(particles_VB); + for (i = 0; i < Atlas1D.Count; i++) { + int partCount = terrain_1DCount[i]; + if (!partCount) continue; + + Gfx_BindTexture(Atlas1D.TexIds[i]); + Gfx_DrawVb_IndexedTris_Range(partCount, offset); + offset += partCount; + } +} + +static void Terrain_RemoveAt(int i) { + for (; i < terrain_count - 1; i++) { + terrain_particles[i] = terrain_particles[i + 1]; + } + terrain_count--; +} + +static void Terrain_Tick(float delta) { + int i; + for (i = 0; i < terrain_count; i++) { + if (TerrainParticle_Tick(&terrain_particles[i], delta)) { + Terrain_RemoveAt(i); i--; + } + } +} + +void Particles_BreakBlockEffect(IVec3 coords, BlockID old, BlockID now) { + struct TerrainParticle* p; + TextureLoc loc; + int texIndex; + TextureRec baseRec, rec; + Vec3 origin, minBB, maxBB; + + /* texture UV variables */ + float uScale, vScale, maxU2, maxV2; + int minX, minZ, maxX, maxZ; + int minU, minV, maxU, maxV; + int maxUsedU, maxUsedV; + + /* per-particle variables */ + float cellX, cellY, cellZ; + Vec3 cell; + int x, y, z, type; + + if (now != BLOCK_AIR || Blocks.Draw[old] == DRAW_GAS) return; + IVec3_ToVec3(&origin, &coords); + loc = Block_Tex(old, FACE_XMIN); + + baseRec = Atlas1D_TexRec(loc, 1, &texIndex); + uScale = (1.0f/16.0f); vScale = (1.0f/16.0f) * Atlas1D.InvTileSize; + + minBB = Blocks.MinBB[old]; maxBB = Blocks.MaxBB[old]; + minX = (int)(minBB.x * 16); maxX = (int)(maxBB.x * 16); + minZ = (int)(minBB.z * 16); maxZ = (int)(maxBB.z * 16); + + minU = min(minX, minZ); minV = (int)(16 - maxBB.y * 16); + maxU = min(maxX, maxZ); maxV = (int)(16 - minBB.y * 16); + /* This way we can avoid creating particles which outside the bounds and need to be clamped */ + maxUsedU = maxU; maxUsedV = maxV; + if (minU < 12 && maxU > 12) maxUsedU = 12; + if (minV < 12 && maxV > 12) maxUsedV = 12; + + #define GRID_SIZE 4 + /* gridOffset gives the centre of the cell on a grid */ + #define CELL_CENTRE ((1.0f / GRID_SIZE) * 0.5f) + + maxU2 = baseRec.u1 + maxU * uScale; + maxV2 = baseRec.v1 + maxV * vScale; + for (x = 0; x < GRID_SIZE; x++) { + for (y = 0; y < GRID_SIZE; y++) { + for (z = 0; z < GRID_SIZE; z++) { + + cellX = (float)x / GRID_SIZE; cellY = (float)y / GRID_SIZE; cellZ = (float)z / GRID_SIZE; + cell = Vec3_Create3(CELL_CENTRE + cellX, CELL_CENTRE / 2 + cellY, CELL_CENTRE + cellZ); + if (cell.x < minBB.x || cell.x > maxBB.x || cell.y < minBB.y + || cell.y > maxBB.y || cell.z < minBB.z || cell.z > maxBB.z) continue; + + if (terrain_count == PARTICLES_MAX) Terrain_RemoveAt(0); + p = &terrain_particles[terrain_count++]; + + /* centre random offset around [-0.2, 0.2] */ + p->base.velocity.x = CELL_CENTRE + (cellX - 0.5f) + (Random_Float(&rnd) * 0.4f - 0.2f); + p->base.velocity.y = CELL_CENTRE + (cellY - 0.0f) + (Random_Float(&rnd) * 0.4f - 0.2f); + p->base.velocity.z = CELL_CENTRE + (cellZ - 0.5f) + (Random_Float(&rnd) * 0.4f - 0.2f); + + rec = baseRec; + rec.u1 = baseRec.u1 + Random_Range(&rnd, minU, maxUsedU) * uScale; + rec.v1 = baseRec.v1 + Random_Range(&rnd, minV, maxUsedV) * vScale; + rec.u2 = rec.u1 + 4 * uScale; + rec.v2 = rec.v1 + 4 * vScale; + rec.u2 = min(rec.u2, maxU2) - 0.01f * uScale; + rec.v2 = min(rec.v2, maxV2) - 0.01f * vScale; + + Vec3_Add(&p->base.lastPos, &origin, &cell); + p->base.nextPos = p->base.lastPos; + p->base.lifetime = 0.3f + Random_Float(&rnd) * 1.2f; + + p->rec = rec; + p->texLoc = loc; + p->block = old; + type = Random_Next(&rnd, 30); + p->base.size = type >= 28 ? 12 : (type >= 25 ? 10 : 8); + } + } + } +} + + +/*########################################################################################################################* +*-------------------------------------------------------Custom particle---------------------------------------------------* +*#########################################################################################################################*/ +#ifdef CC_BUILD_NETWORKING +struct CustomParticle { + struct Particle base; + int effectId; + float totalLifespan; +}; + +struct CustomParticleEffect Particles_CustomEffects[256]; +static struct CustomParticle custom_particles[PARTICLES_MAX]; +static int custom_count; +static cc_uint8 collideFlags; +#define EXPIRES_UPON_TOUCHING_GROUND (1 << 0) +#define SOLID_COLLIDES (1 << 1) +#define LIQUID_COLLIDES (1 << 2) +#define LEAF_COLLIDES (1 << 3) + +static cc_bool CustomParticle_CanPass(BlockID block) { + cc_uint8 draw, collide; + + draw = Blocks.Draw[block]; + if (draw == DRAW_TRANSPARENT_THICK && !(collideFlags & LEAF_COLLIDES)) return true; + + collide = Blocks.Collide[block]; + if (collide == COLLIDE_SOLID && (collideFlags & SOLID_COLLIDES)) return false; + if (collide == COLLIDE_LIQUID && (collideFlags & LIQUID_COLLIDES)) return false; + return true; +} + +static cc_bool CustomParticle_Tick(struct CustomParticle* p, float delta) { + struct CustomParticleEffect* e = &Particles_CustomEffects[p->effectId]; + hitTerrain = false; + collideFlags = e->collideFlags; + + return PhysicsTick(&p->base, e->gravity, CustomParticle_CanPass, delta) + || (hitTerrain && (e->collideFlags & EXPIRES_UPON_TOUCHING_GROUND)); +} + +static void CustomParticle_Render(struct CustomParticle* p, float t, struct VertexTextured* vertices) { + struct CustomParticleEffect* e = &Particles_CustomEffects[p->effectId]; + Vec3 pos; + Vec2 size; + PackedCol col; + TextureRec rec = e->rec; + int x, y, z; + + float time_lived = p->totalLifespan - p->base.lifetime; + int curFrame = Math_Floor(e->frameCount * (time_lived / p->totalLifespan)); + float shiftU = curFrame * (rec.u2 - rec.u1); + + rec.u1 += shiftU;/* * 0.0078125f; */ + rec.u2 += shiftU;/* * 0.0078125f; */ + + Vec3_Lerp(&pos, &p->base.lastPos, &p->base.nextPos, t); + size.x = p->base.size; size.y = size.x; + + x = Math_Floor(pos.x); y = Math_Floor(pos.y); z = Math_Floor(pos.z); + col = e->fullBright ? PACKEDCOL_WHITE : Lighting.Color(x, y, z); + col = PackedCol_Tint(col, e->tintCol); + + Particle_DoRender(&size, &pos, &rec, col, vertices); +} + +static void Custom_Render(float t) { + struct VertexTextured* data; + int i; + if (!custom_count) return; + + data = (struct VertexTextured*)Gfx_LockDynamicVb(particles_VB, + VERTEX_FORMAT_TEXTURED, custom_count * 4); + for (i = 0; i < custom_count; i++) { + CustomParticle_Render(&custom_particles[i], t, data); + data += 4; + } + + Gfx_BindTexture(particles_TexId); + Gfx_UnlockDynamicVb(particles_VB); + Gfx_DrawVb_IndexedTris(custom_count * 4); +} + +static void Custom_RemoveAt(int i) { + for (; i < custom_count - 1; i++) { + custom_particles[i] = custom_particles[i + 1]; + } + custom_count--; +} + +static void Custom_Tick(float delta) { + int i; + for (i = 0; i < custom_count; i++) { + if (CustomParticle_Tick(&custom_particles[i], delta)) { + Custom_RemoveAt(i); i--; + } + } +} + +void Particles_CustomEffect(int effectID, float x, float y, float z, float originX, float originY, float originZ) { + struct CustomParticle* p; + struct CustomParticleEffect* e = &Particles_CustomEffects[effectID]; + int i, count = e->particleCount; + Vec3 offset, delta; + float d; + + for (i = 0; i < count; i++) { + if (custom_count == PARTICLES_MAX) Custom_RemoveAt(0); + p = &custom_particles[custom_count++]; + p->effectId = effectID; + + offset.x = Random_Float(&rnd) - 0.5f; + offset.y = Random_Float(&rnd) - 0.5f; + offset.z = Random_Float(&rnd) - 0.5f; + Vec3_Normalise(&offset); + + /* See https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/ */ + /* 'Using normally distributed random numbers' */ + d = Random_Float(&rnd); + d = Math_Exp2(Math_Log2(d) / 3.0); /* d^1/3 for better distribution */ + d *= e->spread; + + p->base.lastPos.x = x + offset.x * d; + p->base.lastPos.y = y + offset.y * d; + p->base.lastPos.z = z + offset.z * d; + + Vec3 origin = { originX, originY, originZ }; + Vec3_Sub(&delta, &p->base.lastPos, &origin); + Vec3_Normalise(&delta); + + p->base.velocity.x = delta.x * e->speed; + p->base.velocity.y = delta.y * e->speed; + p->base.velocity.z = delta.z * e->speed; + + p->base.nextPos = p->base.lastPos; + p->base.lifetime = e->baseLifetime + (e->baseLifetime * e->lifetimeVariation) * ((Random_Float(&rnd) - 0.5f) * 2); + p->totalLifespan = p->base.lifetime; + + p->base.size = e->size + (e->size * e->sizeVariation) * ((Random_Float(&rnd) - 0.5f) * 2); + + /* Don't spawn custom particle inside a block (otherwise it appears */ + /* for a few frames, then disappears in first PhysicsTick call)*/ + collideFlags = e->collideFlags; + if (IntersectsBlock(&p->base, CustomParticle_CanPass)) custom_count--; + } +} +#else +static int custom_count; + +static void Custom_Render(float t) { } +static void Custom_Tick(float delta) { } +#endif + + +/*########################################################################################################################* +*--------------------------------------------------------Particles--------------------------------------------------------* +*#########################################################################################################################*/ +void Particles_Render(float t) { + if (!terrain_count && !rain_count && !custom_count) return; + + if (Gfx.LostContext) return; + if (!particles_VB) + particles_VB = Gfx_CreateDynamicVb(VERTEX_FORMAT_TEXTURED, PARTICLES_MAX * 4); + + Gfx_SetAlphaTest(true); + + Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED); + Terrain_Render(t); + Rain_Render(t); + Custom_Render(t); + + Gfx_SetAlphaTest(false); +} + +static void Particles_Tick(struct ScheduledTask* task) { + float delta = task->interval; + Terrain_Tick(delta); + Rain_Tick(delta); + Custom_Tick(delta); +} + + +/*########################################################################################################################* +*---------------------------------------------------Particles component---------------------------------------------------* +*#########################################################################################################################*/ +static void ParticlesPngProcess(struct Stream* stream, const cc_string* name) { + Game_UpdateTexture(&particles_TexId, stream, name, NULL, NULL); +} +static struct TextureEntry particles_entry = { "particles.png", ParticlesPngProcess }; + + +static void OnContextLost(void* obj) { + Gfx_DeleteDynamicVb(&particles_VB); + + if (Gfx.ManagedTextures) return; + Gfx_DeleteTexture(&particles_TexId); +} + +static void OnBreakBlockEffect_Handler(void* obj, IVec3 coords, BlockID old, BlockID now) { + Particles_BreakBlockEffect(coords, old, now); +} + +static void OnInit(void) { + ScheduledTask_Add(GAME_DEF_TICKS, Particles_Tick); + Random_SeedFromCurrentTime(&rnd); + TextureEntry_Register(&particles_entry); + + Event_Register_(&UserEvents.BlockChanged, NULL, OnBreakBlockEffect_Handler); + Event_Register_(&GfxEvents.ContextLost, NULL, OnContextLost); +} + +static void OnFree(void) { OnContextLost(NULL); } + +static void OnReset(void) { rain_count = 0; terrain_count = 0; custom_count = 0; } + +struct IGameComponent Particles_Component = { + OnInit, /* Init */ + OnFree, /* Free */ + OnReset, /* Reset */ + OnReset /* OnNewMap */ +}; |