Just commenting to say that I am reading it along. Interesting stuff, please keep writing!

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.
Show posts Menu// new module header
managed struct ParticleDefinition {
int sprite;
int offsetX; // Offset from the emitter position
int offsetY; // Offset from the emitter position
int life; // Lifetime of the particle
int vx; // mili Velocity in x direction
int vy; // mili Velocity in y direction
int gravity; // mili Gravity effect on the particle
int initialSprite;
int finalSprite;
int initialTransparency; // Initial transparency
int finalTransparency; // Final transparency
int initialWidth; // Initial width
int finalWidth; // Final width
int initialHeight; // Initial height
int finalHeight; // Final height
bool groundHitBounces;
int groundY;
};
managed struct Particle {
int sprite;
int x;
int y;
int mx;
int my;
int life;
int initialLife;
int overlayIndex; // This refers to the overlay in the overlay pool
int vx; // x velocity
int vy; // y velocity
int gravity; // this is vertical acceleration downwards
int transparency;
int width;
int height;
int initialSprite;
int rollSprite;
int deltaSprite;
int initialTransparency;
int deltaTransparency;
int initialWidth;
int deltaWidth;
int initialHeight;
int deltaHeight;
bool bounces;
int groundY;
// Initialize the particle with its position, life, velocity, and transparency
import void Init(ParticleDefinition* def, int x, int y, int overlayIndex);
import void Update(); // Update particle position and overlay
import bool IsAlive(); // Check if particle is still alive
import bool IsCollidingPoint(int x, int y);
import bool IsCollidingRect(int x, int y, int width, int height);
};
struct Emitter {
protected int x;
protected int y;
protected int emitParticleCount;
protected int particleCount;
protected Particle* particles[]; // Pool of particles
protected ParticleDefinition* definitions[]; // Array of particle definitions
protected int definitionsCount; // Count of particle definitions
protected int lastEmittedParticle;
/// Set emitter possible particle definitions
import void SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount);
/// Update emitter position
import void SetPosition(int x, int y);
/// Get null terminated array of particles that have collision with the point
import Particle* [] GetParticlesCollidingPoint(int x, int y);
import Particle* [] GetParticlesCollidingRect(int x, int y, int width, int height);
/// Initialize the emitter
import void Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount = 10, int particleCount = 50);
/// Emit a specific particle
import protected bool EmitParticleIndex(int i);
/// Emit a single particle
import protected bool EmitSingleParticle();
/// Emit particles
import void Emit();
/// Update all particles
import void Update();
};
struct ContinuousEmitter extends Emitter {
protected int emitRate;
protected int _emitRateFrame;
protected bool isEmitting;
import void StartEmitting(int emitRate = 11);
import void StopEmitting();
import void UpdateContinuous();
};
/// Global Particle Emitter
import ContinuousEmitter GPE;
// new module script
#define MAX_OVERLAYS 2048
Overlay* overlayPool[MAX_OVERLAYS];
bool overlayUsed[MAX_OVERLAYS];
int lastUsed;
#define MAX_LERP 1024
#define INT_FRAC 1024
// lerp from percent 0 to 1024
int _Lerp(int start, int end, int percent) {
if (percent < 0) percent = 0;
if (percent > MAX_LERP) percent = MAX_LERP;
return start + ((end - start) * percent) / MAX_LERP;
}
int InvalidateOverlay(int index)
{
if(overlayPool[index] != null)
{
overlayPool[index].Transparency = 100;
}
overlayUsed[index] = false; // Mark the overlay slot as free
return -1;
}
// Find an available overlay slot in the pool
function GetAvailableOverlayIndex() {
for (int i = lastUsed; i < MAX_OVERLAYS; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
for (int i = 0; i < lastUsed; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
return -1; // No available overlay
}
void UpdateOverlayFromParticle(Overlay* ovr, Particle* p)
{
ovr.Transparency = p.transparency;
ovr.Graphic = p.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(p.x, p.y, p.width, p.height);
#else
ovr.X = p.x;
ovr.Y = p.y;
ovr.Width = p.width;
ovr.Height = p.height;
#endif
}
void Particle::Init(ParticleDefinition* def, int x, int y, int overlayIndex) {
if(this.overlayIndex >= 0 && this.overlayIndex < MAX_OVERLAYS) {
InvalidateOverlay(this.overlayIndex);
}
this.mx = INT_FRAC*(x + def.offsetX); // Offset based on emitter
this.my = INT_FRAC*(y + def.offsetY); // Offset based on emitter
this.x = this.mx/INT_FRAC;
this.y = this.my/INT_FRAC;
this.life = def.life;
this.vx = (def.vx*INT_FRAC)/1000;
this.vy = (def.vy*INT_FRAC)/1000;
this.gravity = (def.gravity*INT_FRAC)/1000;
this.transparency = def.initialTransparency;
this.initialTransparency = def.initialTransparency;
this.deltaTransparency = def.finalTransparency - def.initialTransparency;
this.width = def.initialWidth;
this.initialWidth = def.initialWidth;
this.deltaWidth = def.finalWidth - def.initialWidth;
this.height = def.initialHeight;
this.initialHeight = def.initialHeight;
this.deltaHeight = def.finalHeight - def.initialHeight;
this.rollSprite = def.sprite - def.initialSprite;
this.sprite = def.sprite;
this.initialSprite = def.initialSprite;
this.deltaSprite = def.finalSprite - def.initialSprite;
this.overlayIndex = overlayIndex;
this.initialLife = def.life; // Store initial life for transitions
this.bounces = def.groundHitBounces;
this.groundY = def.groundY;
if(this.groundY <= 0) {
this.groundY = 16777216; // a big number so it is not reached
}
if (overlayIndex >= 0 && overlayPool[overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[overlayIndex], this);
}
}
bool Particle::IsAlive() {
return (this.life > 0);
}
bool Particle::IsCollidingPoint(int x, int y) {
return (x >= this.x) && (y >= this.y) && (x <= this.x + this.width) && (y <= this.y + this.height);
}
// TODO: check if this correct
bool Particle::IsCollidingRect(int x, int y, int width, int height) {
return (x + width >= this.x) && (y + height >= this.y) && (x <= this.x + this.width) && (y <= this.y + this.height);
}
// Update the particle state and sync with overlay
void Particle::Update() {
// alive check is done before calling this function
this.mx += this.vx;
this.my += this.vy;
this.vy += this.gravity; // Apply gravity
this.x = this.mx/INT_FRAC;
this.y = this.my/INT_FRAC;
// Calculate the scaling and transparency transitions based on life
int percent = MAX_LERP - ((this.life * MAX_LERP) / this.initialLife); // 0 to 1024
this.transparency = this.initialTransparency + ((this.deltaTransparency) * percent) / MAX_LERP;
this.width = this.initialWidth + ((this.deltaWidth) * percent) / MAX_LERP;
this.height = this.initialHeight + ((this.deltaHeight) * percent) / MAX_LERP;
if(this.deltaSprite > 0) {
this.sprite = this.initialSprite + (this.rollSprite + ((this.deltaSprite) * percent) / MAX_LERP) % this.deltaSprite;
}
int oidx = this.overlayIndex;
if (oidx >= 0 && overlayPool[oidx] != null) {
// UpdateOverlayFromParticle(overlayPool[this.overlayIndex], this);
Overlay* ovr = overlayPool[oidx];
ovr.Transparency = this.transparency;
ovr.Graphic = this.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(this.x, this.y, this.width, this.height);
#else
ovr.X = this.x;
ovr.Y = this.y;
ovr.Width = this.width;
ovr.Height = this.height;
#endif
}
this.life--;
if (this.y >= this.groundY) {
if (this.bounces) {
this.vy = -(this.vy * 700)/INT_FRAC; // Invert velocity, reduce it to simulate energy loss
} else {
this.life = 0; // Mark particle as dead (cheaper than other things...)
}
}
}
void Emitter::SetPosition(int x, int y)
{
this.x = x;
this.y = y;
}
void Emitter::SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount)
{
for(int i=0; i<definitionsCount; i++) {
ParticleDefinition* def = definitions[i];
if(def.initialSprite == 0 && def.finalSprite == 0) {
def.initialSprite = def.sprite;
def.finalSprite = def.sprite;
}
}
this.definitions = definitions;
this.definitionsCount = definitionsCount;
}
// Initialize the emitter with position, particle definitions, and specific parameters
void Emitter::Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount, int particleCount) {
this.SetPosition(x, y);
this.particleCount = particleCount;
this.SetParticleDefinitions(definitions, definitionsCount);
this.emitParticleCount = emitParticleCount;
this.particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++) {
this.particles[i] = new Particle;
}
}
protected bool Emitter::EmitParticleIndex(int i)
{
if(this.particles[i].IsAlive())
return false;
this.lastEmittedParticle = i;
// Reuse dead particle if it's not alive anymore
int overlayIndex = GetAvailableOverlayIndex();
if (overlayIndex >= 0) {
// Randomly select a particle definition from the available definitions
int defIndex = 0;
if(this.definitionsCount > 0) {
defIndex = Random(this.definitionsCount-1);
}
ParticleDefinition* def = this.definitions[defIndex];
if(overlayPool[overlayIndex] == null) {
Overlay* ovr = Overlay.CreateGraphical(this.x, this.y, def.sprite);
overlayPool[overlayIndex] = ovr;
}
this.particles[i].Init(def, this.x, this.y, overlayIndex);
}
return true;
}
// Emit a single particle from the emitter
protected bool Emitter::EmitSingleParticle() {
//System.Log(eLogInfo, ">>begin sp");
int i = (this.lastEmittedParticle + 1) % this.particleCount;
if(this.EmitParticleIndex(i))
return false;
//System.Log(eLogInfo, "loop1 sp");
int loop_at = i;
for (; i < this.particleCount; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
//System.Log(eLogInfo, "loop2 sp");
for (i=0; i < loop_at; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
// FIX-ME: if we get here we need to do some sort of cooldown
// the issue is we are emitting too many particles per total particle count
// along with particles that have a too big life
return true; // indicates something is wrong
}
// Emit particles from the emitter
void Emitter::Emit() {
for (int i = 0; i < this.emitParticleCount; i++) {
if(this.EmitSingleParticle())
return;
}
}
// Update all particles
void Emitter::Update() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].life > 0) {
this.particles[i].Update();
}
else if (this.particles[i].overlayIndex >= 0)
{
this.particles[i].overlayIndex = InvalidateOverlay(this.particles[i].overlayIndex);
}
}
}
Particle* [] Emitter::GetParticlesCollidingPoint(int x, int y)
{
int c[] = new int[this.particleCount];
int c_count;
for (int i = 0; i < this.particleCount; i++) {
Particle* p = this.particles[i];
if(p.IsAlive() && p.IsCollidingPoint(x, y)) {
c[c_count] = i;
c_count++;
}
}
Particle* ps [] = new Particle[c_count + 1];
for (int i = 0; i < c_count; i++) {
ps[i] = this.particles[c[i]];
}
return ps;
}
Particle* [] Emitter::GetParticlesCollidingRect(int x, int y, int width, int height)
{
int c[] = new int[this.particleCount];
int c_count = 0;
for (int i = 0; i < this.particleCount; i++) {
Particle* p = this.particles[i];
if(p.IsAlive() && p.IsCollidingRect(x, y, width, height)) {
c[c_count] = i;
c_count++;
}
}
Particle* ps [] = new Particle[c_count + 1];
for (int i = 0; i < c_count; i++) {
ps[i] = this.particles[c[i]];
}
return ps;
}
void ContinuousEmitter::StartEmitting(int emitRate)
{
if(emitRate < 4) emitRate = 4;
this.emitRate = emitRate;
this.isEmitting = true;
}
void ContinuousEmitter::StopEmitting()
{
this.isEmitting = false;
}
void ContinuousEmitter::UpdateContinuous()
{
this.Update();
if(this.isEmitting) {
this._emitRateFrame--;
if(this._emitRateFrame <= 0) {
this._emitRateFrame = this.emitRate;
this.Emit();
}
}
}
ContinuousEmitter GPE;
export GPE;
void game_start()
{
ParticleDefinition* d[];
int max_particles = (MAX_OVERLAYS*4)/5;
GPE.Init(Screen.Width/2, Screen.Height/2, d, 0, max_particles/16, max_particles);
}
void repeatedly_execute_always()
{
GPE.UpdateContinuous();
}
// room script file
//Emitter emt;
function hGlowingOrb_Look(Hotspot *thisHotspot, CursorMode mode)
{
player.Say("It is the second best glowing orb that I've seen today.");
}
ParticleDefinition* GetFireworksParticle()
{
ParticleDefinition* fireworksParticle = new ParticleDefinition;
fireworksParticle.life = 40;
// make the velocities circular
float r = 2000.0*Maths.Sqrt(IntToFloat(Random(8192))/8192.0);
float theta = 2.0 * Maths.Pi*(IntToFloat(Random(8192))/8192.0);
fireworksParticle.vx = FloatToInt(r * Maths.Cos(theta)); // Random outward velocity
fireworksParticle.vy = FloatToInt(r * Maths.Sin(theta));
fireworksParticle.gravity = 0; // No gravity
fireworksParticle.initialTransparency = 0;
fireworksParticle.finalTransparency = 100;
fireworksParticle.initialWidth = 2;
fireworksParticle.finalWidth = 20; // Expanding outward
fireworksParticle.initialHeight = 2;
fireworksParticle.finalHeight = 20;
return fireworksParticle;
}
ParticleDefinition* GetSparkleParticle()
{
ParticleDefinition* sparkleParticle = new ParticleDefinition;
sparkleParticle.life = 50;
sparkleParticle.vx = Random(3000) - 1000;
sparkleParticle.vy = Random(3000) - 1000;
sparkleParticle.initialTransparency = 0;
sparkleParticle.finalTransparency = 100;
sparkleParticle.initialWidth = 3;
sparkleParticle.finalWidth = 8;
sparkleParticle.initialHeight = 3;
sparkleParticle.finalHeight = 8;
sparkleParticle.gravity = 100;
sparkleParticle.groundY = 154;
sparkleParticle.groundHitBounces = true;
return sparkleParticle;
}
ParticleDefinition* GetExplosionParticle()
{
ParticleDefinition* explosionParticle = new ParticleDefinition;
explosionParticle.sprite = 1+ Random(60);
explosionParticle.initialSprite = 1;
explosionParticle.finalSprite = 61;
explosionParticle.life = 40;
explosionParticle.vx = Random(3000) - 1500;
explosionParticle.vy = Random(3000) - 1500;
explosionParticle.gravity = -90;
explosionParticle.initialTransparency = 15;
explosionParticle.finalTransparency = 100;
explosionParticle.initialWidth = 12;
explosionParticle.finalWidth = 50;
explosionParticle.initialHeight = 12;
explosionParticle.finalHeight = 50;
return explosionParticle;
}
ParticleDefinition* GetSmokeParticle()
{
ParticleDefinition* smokeParticle = new ParticleDefinition;
smokeParticle.life = 40+Random(14);
smokeParticle.vy = -1000-Random(1000);
smokeParticle.initialTransparency = 0;
smokeParticle.finalTransparency = 100;
smokeParticle.initialWidth = 10+Random(2);
smokeParticle.finalWidth = 20+Random(2);
smokeParticle.initialHeight = 20+Random(2);
smokeParticle.finalHeight = 10+Random(2);
return smokeParticle;
}
ParticleDefinition* GetBubbleParticle()
{
ParticleDefinition* bubbleParticle = new ParticleDefinition;
bubbleParticle.life = 60;
bubbleParticle.vx = Random(500) - 250; // Small horizontal drift
bubbleParticle.vy = -1000 - Random(500); // Rising upwards
bubbleParticle.gravity = -200; // Rising effect
bubbleParticle.initialTransparency = 30;
bubbleParticle.finalTransparency = 100;
bubbleParticle.initialWidth = 5;
bubbleParticle.finalWidth = 15; // Expands as it rises
bubbleParticle.initialHeight = 5;
bubbleParticle.finalHeight = 15;
return bubbleParticle;
}
ParticleDefinition* GetRainParticle()
{
ParticleDefinition* rainParticle = new ParticleDefinition;
rainParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
rainParticle.offsetY = -Random(30);
rainParticle.life = 50;
rainParticle.vx = Random(500) - 250; // Slight horizontal movement
rainParticle.vy = 3000; // Falling down quickly
rainParticle.gravity = 180; // Light gravity effect
rainParticle.initialTransparency = 30;
rainParticle.finalTransparency = 80;
rainParticle.initialWidth = 2;
rainParticle.finalWidth = 2;
rainParticle.initialHeight = 10;
rainParticle.finalHeight = 15; // Lengthening as it falls
return rainParticle;
}
ParticleDefinition* GetFireParticle()
{
ParticleDefinition* fireParticle = new ParticleDefinition;
fireParticle.life = 35;
fireParticle.vx = Random(1000) - 500; // Small horizontal variance
fireParticle.vy = -1200 - Random(500); // Rising upward
fireParticle.gravity = -50; // Slow upward pull
fireParticle.initialTransparency = 50;
fireParticle.finalTransparency = 100; // Disappears as it rises
fireParticle.initialWidth = 10;
fireParticle.finalWidth = 20; // Expands as it rises
fireParticle.initialHeight = 10;
fireParticle.finalHeight = 15;
return fireParticle;
}
ParticleDefinition* GetSnowParticle()
{
ParticleDefinition* snowParticle = new ParticleDefinition;
snowParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
snowParticle.offsetY = -Random(30);
snowParticle.life = 160;
snowParticle.vx = Random(300) - 150; // Slight horizontal drift
snowParticle.vy = Random(300) + 220; // Slow downward movement
snowParticle.gravity = 10; // Minimal gravity effect
snowParticle.initialTransparency = 50;
snowParticle.finalTransparency = 75;
snowParticle.initialWidth = 4;
snowParticle.finalWidth = 6; // Slight expansion as it falls
snowParticle.initialHeight = 4;
snowParticle.finalHeight = 6;
return snowParticle;
}
ParticleDefinition* GetSplashParticle()
{
ParticleDefinition* p = new ParticleDefinition;
p.life = 96;
// squished circle
float r = 2000.0*Maths.Sqrt(IntToFloat(Random(8192))/8192.0);
float theta = 2.0 * Maths.Pi*(IntToFloat(Random(8192))/8192.0);
p.vx = FloatToInt(r * Maths.Cos(theta))/3;
p.vy = FloatToInt(r * Maths.Sin(theta))/2-2000; // vy shots upwards
p.initialTransparency = 20;
p.finalTransparency = 100;
p.initialWidth = 2;
p.finalWidth = 8;
p.initialHeight = 8;
p.finalHeight = 2;
p.gravity = 100;
p.groundY = 150 + Random(9);
p.groundHitBounces = true;
return p;
}
enum PresetParticleType {
ePPT_Fireworks,
ePPT_Sparkle,
ePPT_Explosion,
ePPT_Smoke,
ePPT_Bubble,
ePPT_Rain,
ePPT_Fire,
ePPT_Snow,
ePPT_Splash
};
#define ePPT_Last ePPT_Splash
ParticleDefinition* [] GetParticleDefinitionsArrayByType(PresetParticleType type, int count)
{
ParticleDefinition* definitions[] = new ParticleDefinition[count];
int i;
switch(type) {
case ePPT_Fireworks:
for(i=0; i<count; i++)
{
definitions[i] = GetFireworksParticle();
}
break;
case ePPT_Sparkle:
for(i=0; i<count; i++)
{
definitions[i] = GetSparkleParticle();
}
break;
case ePPT_Explosion:
for(i=0; i<count; i++)
{
definitions[i] = GetExplosionParticle();
}
break;
case ePPT_Smoke:
for(i=0; i<count; i++)
{
definitions[i] = GetSmokeParticle();
}
break;
case ePPT_Bubble:
for(i=0; i<count; i++)
{
definitions[i] = GetBubbleParticle();
}
break;
case ePPT_Rain:
for(i=0; i<count; i++)
{
definitions[i] = GetRainParticle();
}
break;
case ePPT_Fire:
for(i=0; i<count; i++)
{
definitions[i] = GetFireParticle();
}
break;
case ePPT_Snow:
for(i=0; i<count; i++)
{
definitions[i] = GetSnowParticle();
}
break;
case ePPT_Splash:
for(i=0; i<count; i++)
{
definitions[i] = GetSplashParticle();
}
break;
}
return definitions;
}
String GetTypeName(PresetParticleType type) {
switch(type) {
case ePPT_Fireworks:
return "Fireworks";
case ePPT_Sparkle:
return "Sparkle";
case ePPT_Explosion:
return "Explosion";
case ePPT_Smoke:
return "Smoke";
case ePPT_Bubble:
return "Bubble";
case ePPT_Rain:
return "Rain";
case ePPT_Fire:
return "Fire";
case ePPT_Snow:
return "Snow";
case ePPT_Splash:
return "Splash";
default:
return "Unknown";
}
}
void SetEmitterToType(PresetParticleType type)
{
int definitions_count = 2048;
ParticleDefinition* definitions[] = GetParticleDefinitionsArrayByType(type, definitions_count);
GPE.SetParticleDefinitions(definitions, definitions_count);
lbl_particle_selected.Text = GetTypeName(type);
if(type == ePPT_Rain || type == ePPT_Snow) {
GPE.SetPosition(Screen.Width/2, 0);
GPE.StartEmitting();
} else {
GPE.StopEmitting();
}
}
void ParticlePlayerCollision()
{
ViewFrame* c_vf = Game.GetViewFrame(player.NormalView, 0, 0);
float scaling = IntToFloat(player.Scaling) / 100.00;
int p_rect_width = FloatToInt(IntToFloat(Game.SpriteWidth[c_vf.Graphic]) * scaling);
int p_rect_height = FloatToInt(IntToFloat(Game.SpriteHeight[c_vf.Graphic]) * scaling);
int p_rect_x = player.x - p_rect_width/2;
int p_rect_y = player.y - p_rect_height;
//DrawingSurface* surf = Room.GetDrawingSurfaceForBackground();
//surf.DrawingColor = Random(65535);
//surf.DrawRectangle(p_rect_x, p_rect_y, p_rect_x + p_rect_width, p_rect_y + p_rect_height);
//surf.Release();
Particle* pcp [] = GPE.GetParticlesCollidingRect(p_rect_x, p_rect_y, p_rect_width, p_rect_height);
for(int i=0; pcp[i] != null; i++)
pcp[i].life-=2;
}
int particle_type;
void on_call (int value)
{
if(value == 1) {
particle_type++;
if(particle_type> ePPT_Last) {
particle_type = 1;
}
}
SetEmitterToType(particle_type);
}
function room_Load()
{
particle_type = ePPT_Splash;
SetEmitterToType(particle_type);
}
void on_mouse_click(MouseButton button)
{
if(particle_type == ePPT_Rain || particle_type == ePPT_Snow)
return;
GPE.SetPosition(mouse.x, mouse.y);
GPE.Emit();
}
int mb_press;
function room_RepExec()
{
ParticlePlayerCollision();
if(particle_type == ePPT_Rain || particle_type == ePPT_Snow)
return;
if(mouse.IsButtonDown(eMouseLeft))
mb_press++;
else
mb_press = 0;
if(mb_press > 10) {
GPE.SetPosition(mouse.x, mouse.y);
GPE.StartEmitting(5);
} else {
GPE.StopEmitting();
}
}
function room_AfterFadeIn()
{
}
Quote from: Dave Gilbert on Wed 16/10/2024 15:35:13The demo version of the game was set to put its savefiles in the same folder as the main game.
ParticleDefinition* GetFireworksParticle()
{
ParticleDefinition* fireworksParticle = new ParticleDefinition;
fireworksParticle.life = 40;
// make the velocities circular <<-- this is the new thing!
float r = 2000.0*Maths.Sqrt(IntToFloat(Random(8192))/8192.0);
float theta = 2.0 * Maths.Pi*(IntToFloat(Random(8192))/8192.0);
fireworksParticle.vx = FloatToInt(r * Maths.Cos(theta)); // Random outward velocity
fireworksParticle.vy = FloatToInt(r * Maths.Sin(theta));
fireworksParticle.gravity = 0; // No gravity
fireworksParticle.initialTransparency = 0;
fireworksParticle.finalTransparency = 100;
fireworksParticle.initialWidth = 2;
fireworksParticle.finalWidth = 20; // Expanding outward
fireworksParticle.initialHeight = 2;
fireworksParticle.finalHeight = 20;
return fireworksParticle;
}
// new module header
managed struct ParticleDefinition {
int sprite;
int offsetX; // Offset from the emitter position
int offsetY; // Offset from the emitter position
int life; // Lifetime of the particle
int vx; // mili Velocity in x direction
int vy; // mili Velocity in y direction
int gravity; // mili Gravity effect on the particle
int initialSprite;
int finalSprite;
int initialTransparency; // Initial transparency
int finalTransparency; // Final transparency
int initialWidth; // Initial width
int finalWidth; // Final width
int initialHeight; // Initial height
int finalHeight; // Final height
bool groundHitBounces;
int groundY;
};
managed struct Particle {
int sprite;
int x;
int y;
int life;
int initialLife;
int overlayIndex; // This refers to the overlay in the overlay pool
int vx; // x velocity
int vy; // y velocity
int gravity; // this is vertical acceleration downwards
int transparency;
int width;
int height;
int initialSprite;
int rollSprite;
int deltaSprite;
int initialTransparency;
int deltaTransparency;
int initialWidth;
int deltaWidth;
int initialHeight;
int deltaHeight;
bool bounces;
int groundY;
// Initialize the particle with its position, life, velocity, and transparency
import void Init(ParticleDefinition* def, int x, int y, int overlayIndex);
import void Update(); // Update particle position and overlay
import bool IsAlive(); // Check if particle is still alive
};
struct Emitter {
protected int x;
protected int y;
protected int emitParticleCount;
protected int particleCount;
protected Particle* particles[]; // Pool of particles
protected ParticleDefinition* definitions[]; // Array of particle definitions
protected int definitionsCount; // Count of particle definitions
protected int lastEmittedParticle;
/// Set emitter possible particle definitions
import void SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount);
/// Update emitter position
import void SetPosition(int x, int y);
/// Initialize the emitter
import void Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount = 10, int particleCount = 50);
/// Emit a specific particle
import protected bool EmitParticleIndex(int i);
/// Emit a single particle
import protected bool EmitSingleParticle();
/// Emit particles
import void Emit();
/// Update all particles
import void Update();
};
struct ContinuousEmitter extends Emitter {
protected int emitRate;
protected int _emitRateFrame;
protected bool isEmitting;
import void StartEmitting(int emitRate = 11);
import void StopEmitting();
import void UpdateContinuous();
};
/// Global Particle Emitter
import ContinuousEmitter GPE;
// new module script
#define MAX_OVERLAYS 2048
Overlay* overlayPool[MAX_OVERLAYS];
bool overlayUsed[MAX_OVERLAYS];
int lastUsed;
#define MAX_LERP 1024
#define INT_FRAC 1024
// lerp from percent 0 to 1024
int _Lerp(int start, int end, int percent) {
if (percent < 0) percent = 0;
if (percent > MAX_LERP) percent = MAX_LERP;
return start + ((end - start) * percent) / MAX_LERP;
}
int InvalidateOverlay(int index)
{
if(overlayPool[index] != null)
{
overlayPool[index].Transparency = 100;
}
overlayUsed[index] = false; // Mark the overlay slot as free
return -1;
}
// Find an available overlay slot in the pool
function GetAvailableOverlayIndex() {
for (int i = lastUsed; i < MAX_OVERLAYS; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
for (int i = 0; i < lastUsed; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
return -1; // No available overlay
}
void UpdateOverlayFromParticle(Overlay* ovr, Particle* p)
{
ovr.Transparency = p.transparency;
ovr.Graphic = p.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(p.x/INT_FRAC, p.y/INT_FRAC, p.width, p.height);
#else
ovr.X = p.x/INT_FRAC;
ovr.Y = p.y/INT_FRAC;
ovr.Width = p.width;
ovr.Height = p.height;
#endif
}
void Particle::Init(ParticleDefinition* def, int x, int y, int overlayIndex) {
if(this.overlayIndex >= 0 && this.overlayIndex < MAX_OVERLAYS) {
InvalidateOverlay(this.overlayIndex);
}
this.x = INT_FRAC*(x + def.offsetX); // Offset based on emitter
this.y = INT_FRAC*(y + def.offsetY); // Offset based on emitter
this.life = def.life;
this.vx = (def.vx*INT_FRAC)/1000;
this.vy = (def.vy*INT_FRAC)/1000;
this.gravity = (def.gravity*INT_FRAC)/1000;
this.transparency = def.initialTransparency;
this.initialTransparency = def.initialTransparency;
this.deltaTransparency = def.finalTransparency - def.initialTransparency;
this.width = def.initialWidth;
this.initialWidth = def.initialWidth;
this.deltaWidth = def.finalWidth - def.initialWidth;
this.height = def.initialHeight;
this.initialHeight = def.initialHeight;
this.deltaHeight = def.finalHeight - def.initialHeight;
this.rollSprite = def.sprite - def.initialSprite;
this.sprite = def.sprite;
this.initialSprite = def.initialSprite;
this.deltaSprite = def.finalSprite - def.initialSprite;
this.overlayIndex = overlayIndex;
this.initialLife = def.life; // Store initial life for transitions
this.bounces = def.groundHitBounces;
this.groundY = def.groundY*INT_FRAC;
if(this.groundY <= 0) {
this.groundY = 16777216; // a big number so it is not reached
}
if (overlayIndex >= 0 && overlayPool[overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[overlayIndex], this);
}
}
bool Particle::IsAlive() {
return (this.life > 0);
}
// Update the particle state and sync with overlay
void Particle::Update() {
// alive check is done before calling this function
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity; // Apply gravity
int px = this.x/INT_FRAC;
int py = this.y/INT_FRAC;
// Calculate the scaling and transparency transitions based on life
int percent = MAX_LERP - ((this.life * MAX_LERP) / this.initialLife); // 0 to 1024
this.transparency = this.initialTransparency + ((this.deltaTransparency) * percent) / MAX_LERP;
this.width = this.initialWidth + ((this.deltaWidth) * percent) / MAX_LERP;
this.height = this.initialHeight + ((this.deltaHeight) * percent) / MAX_LERP;
if(this.deltaSprite > 0) {
this.sprite = this.initialSprite + (this.rollSprite + ((this.deltaSprite) * percent) / MAX_LERP) % this.deltaSprite;
}
int oidx = this.overlayIndex;
if (oidx >= 0 && overlayPool[oidx] != null) {
// UpdateOverlayFromParticle(overlayPool[this.overlayIndex], this);
Overlay* ovr = overlayPool[oidx];
ovr.Transparency = this.transparency;
ovr.Graphic = this.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(px, py, this.width, this.height);
#else
ovr.X = px;
ovr.Y = py;
ovr.Width = this.width;
ovr.Height = this.height;
#endif
}
this.life--;
if (this.y >= this.groundY) {
if (this.bounces) {
this.vy = -(this.vy * 700)/INT_FRAC; // Invert velocity, reduce it to simulate energy loss
} else {
this.life = 0; // Mark particle as dead (cheaper than other things...)
}
}
}
void Emitter::SetPosition(int x, int y)
{
this.x = x;
this.y = y;
}
void Emitter::SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount)
{
for(int i=0; i<definitionsCount; i++) {
ParticleDefinition* def = definitions[i];
if(def.initialSprite == 0 && def.finalSprite == 0) {
def.initialSprite = def.sprite;
def.finalSprite = def.sprite;
}
}
this.definitions = definitions;
this.definitionsCount = definitionsCount;
}
// Initialize the emitter with position, particle definitions, and specific parameters
void Emitter::Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount, int particleCount) {
this.SetPosition(x, y);
this.particleCount = particleCount;
this.SetParticleDefinitions(definitions, definitionsCount);
this.emitParticleCount = emitParticleCount;
this.particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++) {
this.particles[i] = new Particle;
}
}
protected bool Emitter::EmitParticleIndex(int i)
{
if(this.particles[i].IsAlive())
return false;
this.lastEmittedParticle = i;
// Reuse dead particle if it's not alive anymore
int overlayIndex = GetAvailableOverlayIndex();
if (overlayIndex >= 0) {
// Randomly select a particle definition from the available definitions
int defIndex = 0;
if(this.definitionsCount > 0) {
defIndex = Random(this.definitionsCount-1);
}
ParticleDefinition* def = this.definitions[defIndex];
if(overlayPool[overlayIndex] == null) {
Overlay* ovr = Overlay.CreateGraphical(this.x, this.y, def.sprite);
overlayPool[overlayIndex] = ovr;
}
this.particles[i].Init(def, this.x, this.y, overlayIndex);
}
return true;
}
// Emit a single particle from the emitter
protected bool Emitter::EmitSingleParticle() {
//System.Log(eLogInfo, ">>begin sp");
int i = (this.lastEmittedParticle + 1) % this.particleCount;
if(this.EmitParticleIndex(i))
return false;
//System.Log(eLogInfo, "loop1 sp");
int loop_at = i;
for (; i < this.particleCount; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
//System.Log(eLogInfo, "loop2 sp");
for (i=0; i < loop_at; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
// FIX-ME: if we get here we need to do some sort of cooldown
// the issue is we are emitting too many particles per total particle count
// along with particles that have a too big life
return true; // indicates something is wrong
}
// Emit particles from the emitter
void Emitter::Emit() {
for (int i = 0; i < this.emitParticleCount; i++) {
if(this.EmitSingleParticle())
return;
}
}
// Update all particles
void Emitter::Update() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].life > 0) {
this.particles[i].Update();
}
else if (this.particles[i].overlayIndex >= 0)
{
this.particles[i].overlayIndex = InvalidateOverlay(this.particles[i].overlayIndex);
}
}
}
void ContinuousEmitter::StartEmitting(int emitRate)
{
if(emitRate < 4) emitRate = 4;
this.emitRate = emitRate;
this.isEmitting = true;
}
void ContinuousEmitter::StopEmitting()
{
this.isEmitting = false;
}
void ContinuousEmitter::UpdateContinuous()
{
this.Update();
if(this.isEmitting) {
this._emitRateFrame--;
if(this._emitRateFrame <= 0) {
this._emitRateFrame = this.emitRate;
this.Emit();
}
}
}
ContinuousEmitter GPE;
export GPE;
void game_start()
{
ParticleDefinition* d[];
int max_particles = (MAX_OVERLAYS*4)/5;
GPE.Init(Screen.Width/2, Screen.Height/2, d, 0, max_particles/16, max_particles);
}
void repeatedly_execute_always()
{
GPE.UpdateContinuous();
}
ParticleDefinition* GetExplosionParticle()
{
ParticleDefinition* explosionParticle = new ParticleDefinition;
explosionParticle.sprite = 1+ Random(60);
explosionParticle.initialSprite = 1;
explosionParticle.finalSprite = 61;
explosionParticle.life = 40;
explosionParticle.vx = Random(3000) - 1500;
explosionParticle.vy = Random(3000) - 1500;
explosionParticle.gravity = -90;
explosionParticle.initialTransparency = 15;
explosionParticle.finalTransparency = 100;
explosionParticle.initialWidth = 12;
explosionParticle.finalWidth = 50;
explosionParticle.initialHeight = 12;
explosionParticle.finalHeight = 50;
return explosionParticle;
}
// new module header
managed struct ParticleDefinition {
int sprite;
int offsetX; // Offset from the emitter position
int offsetY; // Offset from the emitter position
int life; // Lifetime of the particle
int vx; // mili Velocity in x direction
int vy; // mili Velocity in y direction
int gravity; // mili Gravity effect on the particle
int initialTransparency; // Initial transparency
int finalTransparency; // Final transparency
int initialWidth; // Initial width
int finalWidth; // Final width
int initialHeight; // Initial height
int finalHeight; // Final height
bool groundHitBounces;
int groundY;
};
managed struct Particle {
int sprite;
int x;
int y;
int life;
int initialLife;
int overlayIndex; // This refers to the overlay in the overlay pool
int vx; // x velocity
int vy; // y velocity
int gravity; // this is vertical acceleration downwards
int transparency;
int width;
int height;
int initialTransparency;
int deltaTransparency;
int initialWidth;
int deltaWidth;
int initialHeight;
int deltaHeight;
bool bounces;
int groundY;
// Initialize the particle with its position, life, velocity, and transparency
import void Init(ParticleDefinition* def, int x, int y, int overlayIndex);
import void Update(); // Update particle position and overlay
import bool IsAlive(); // Check if particle is still alive
};
struct Emitter {
protected int x;
protected int y;
protected int emitParticleCount;
protected int particleCount;
protected Particle* particles[]; // Pool of particles
protected ParticleDefinition* definitions[]; // Array of particle definitions
protected int definitionsCount; // Count of particle definitions
protected int lastEmittedParticle;
/// Set emitter possible particle definitions
import void SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount);
/// Update emitter position
import void SetPosition(int x, int y);
/// Initialize the emitter
import void Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount = 10, int particleCount = 50);
/// Emit a specific particle
import protected bool EmitParticleIndex(int i);
/// Emit a single particle
import protected bool EmitSingleParticle();
/// Emit particles
import void Emit();
/// Update all particles
import void Update();
};
struct ContinuousEmitter extends Emitter {
protected int emitRate;
protected int _emitRateFrame;
protected bool isEmitting;
import void StartEmitting(int emitRate = 11);
import void StopEmitting();
import void UpdateContinuous();
};
/// Global Particle Emitter
import ContinuousEmitter GPE;
// new module script
#define MAX_OVERLAYS 2048
Overlay* overlayPool[MAX_OVERLAYS];
bool overlayUsed[MAX_OVERLAYS];
int lastUsed;
#define MAX_LERP 1024
#define INT_FRAC 1024
// lerp from percent 0 to 1024
int _Lerp(int start, int end, int percent) {
if (percent < 0) percent = 0;
if (percent > MAX_LERP) percent = MAX_LERP;
return start + ((end - start) * percent) / MAX_LERP;
}
int InvalidateOverlay(int index)
{
if(overlayPool[index] != null)
{
overlayPool[index].Transparency = 100;
}
overlayUsed[index] = false; // Mark the overlay slot as free
return -1;
}
// Find an available overlay slot in the pool
function GetAvailableOverlayIndex() {
for (int i = lastUsed; i < MAX_OVERLAYS; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
for (int i = 0; i < lastUsed; i++) {
if (!overlayUsed[i]) {
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
return -1; // No available overlay
}
void UpdateOverlayFromParticle(Overlay* ovr, Particle* p)
{
ovr.Transparency = p.transparency;
ovr.Graphic = p.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(p.x/INT_FRAC, p.y/INT_FRAC, p.width, p.height);
#else
ovr.X = p.x/INT_FRAC;
ovr.Y = p.y/INT_FRAC;
ovr.Width = p.width;
ovr.Height = p.height;
#endif
}
void Particle::Init(ParticleDefinition* def, int x, int y, int overlayIndex) {
if(this.overlayIndex >= 0 && this.overlayIndex < MAX_OVERLAYS) {
InvalidateOverlay(this.overlayIndex);
}
this.x = INT_FRAC*(x + def.offsetX); // Offset based on emitter
this.y = INT_FRAC*(y + def.offsetY); // Offset based on emitter
this.life = def.life;
this.vx = (def.vx*INT_FRAC)/1000;
this.vy = (def.vy*INT_FRAC)/1000;
this.gravity = (def.gravity*INT_FRAC)/1000;
this.transparency = def.initialTransparency;
this.initialTransparency = def.initialTransparency;
this.deltaTransparency = def.finalTransparency - def.initialTransparency;
this.width = def.initialWidth;
this.initialWidth = def.initialWidth;
this.deltaWidth = def.finalWidth - def.initialWidth;
this.height = def.initialHeight;
this.initialHeight = def.initialHeight;
this.deltaHeight = def.finalHeight - def.initialHeight;
this.overlayIndex = overlayIndex;
this.initialLife = def.life; // Store initial life for transitions
this.bounces = def.groundHitBounces;
this.groundY = def.groundY*INT_FRAC;
if(this.groundY <= 0) {
this.groundY = 16777216; // a big number so it is not reached
}
if (overlayIndex >= 0 && overlayPool[overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[overlayIndex], this);
}
}
bool Particle::IsAlive() {
return (this.life > 0);
}
// Update the particle state and sync with overlay
void Particle::Update() {
// alive check is done before calling this function
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity; // Apply gravity
int px = this.x/INT_FRAC;
int py = this.y/INT_FRAC;
// Calculate the scaling and transparency transitions based on life
int percent = MAX_LERP - ((this.life * MAX_LERP) / this.initialLife); // 0 to 1024
this.transparency = this.initialTransparency + ((this.deltaTransparency) * percent) / MAX_LERP;
this.width = this.initialWidth + ((this.deltaWidth) * percent) / MAX_LERP;
this.height = this.initialHeight + ((this.deltaHeight) * percent) / MAX_LERP;
int oidx = this.overlayIndex;
if (oidx >= 0 && overlayPool[oidx] != null) {
// UpdateOverlayFromParticle(overlayPool[this.overlayIndex], this);
Overlay* ovr = overlayPool[oidx];
ovr.Transparency = this.transparency;
ovr.Graphic = this.sprite;
#ifdef SCRIPT_API_v362
ovr.SetPosition(px, py, this.width, this.height);
#else
ovr.X = px;
ovr.Y = py;
ovr.Width = this.width;
ovr.Height = this.height;
#endif
}
this.life--;
if (this.y >= this.groundY) {
if (this.bounces) {
this.vy = -(this.vy * 700)/INT_FRAC; // Invert velocity, reduce it to simulate energy loss
} else {
this.life = 0; // Mark particle as dead (cheaper than other things...)
}
}
}
void Emitter::SetPosition(int x, int y)
{
this.x = x;
this.y = y;
}
void Emitter::SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount)
{
this.definitions = definitions;
this.definitionsCount = definitionsCount;
}
// Initialize the emitter with position, particle definitions, and specific parameters
void Emitter::Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount, int particleCount) {
this.SetPosition(x, y);
this.particleCount = particleCount;
this.SetParticleDefinitions(definitions, definitionsCount);
this.emitParticleCount = emitParticleCount;
this.particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++) {
this.particles[i] = new Particle;
}
}
protected bool Emitter::EmitParticleIndex(int i)
{
if(this.particles[i].IsAlive())
return false;
this.lastEmittedParticle = i;
// Reuse dead particle if it's not alive anymore
int overlayIndex = GetAvailableOverlayIndex();
if (overlayIndex >= 0) {
// Randomly select a particle definition from the available definitions
int defIndex = 0;
if(this.definitionsCount > 0) {
defIndex = Random(this.definitionsCount-1);
}
ParticleDefinition* def = this.definitions[defIndex];
if(overlayPool[overlayIndex] == null) {
Overlay* ovr = Overlay.CreateGraphical(this.x, this.y, def.sprite);
overlayPool[overlayIndex] = ovr;
}
this.particles[i].Init(def, this.x, this.y, overlayIndex);
}
return true;
}
// Emit a single particle from the emitter
protected bool Emitter::EmitSingleParticle() {
//System.Log(eLogInfo, ">>begin sp");
int i = (this.lastEmittedParticle + 1) % this.particleCount;
if(this.EmitParticleIndex(i))
return false;
//System.Log(eLogInfo, "loop1 sp");
int loop_at = i;
for (; i < this.particleCount; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
//System.Log(eLogInfo, "loop2 sp");
for (i=0; i < loop_at; i++) {
if (!this.particles[i].IsAlive())
{
this.EmitParticleIndex(i);
return false;
}
}
// FIX-ME: if we get here we need to do some sort of cooldown
// the issue is we are emitting too many particles per total particle count
// along with particles that have a too big life
return true; // indicates something is wrong
}
// Emit particles from the emitter
void Emitter::Emit() {
for (int i = 0; i < this.emitParticleCount; i++) {
if(this.EmitSingleParticle())
return;
}
}
// Update all particles
void Emitter::Update() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].life > 0) {
this.particles[i].Update();
}
else if (this.particles[i].overlayIndex >= 0)
{
this.particles[i].overlayIndex = InvalidateOverlay(this.particles[i].overlayIndex);
}
}
}
void ContinuousEmitter::StartEmitting(int emitRate)
{
if(emitRate < 4) emitRate = 4;
this.emitRate = emitRate;
this.isEmitting = true;
}
void ContinuousEmitter::StopEmitting()
{
this.isEmitting = false;
}
void ContinuousEmitter::UpdateContinuous()
{
this.Update();
if(this.isEmitting) {
this._emitRateFrame--;
if(this._emitRateFrame <= 0) {
this._emitRateFrame = this.emitRate;
this.Emit();
}
}
}
ContinuousEmitter GPE;
export GPE;
void game_start()
{
ParticleDefinition* d[];
int max_particles = MAX_OVERLAYS;
GPE.Init(Screen.Width/2, Screen.Height/2, d, 0, max_particles/16, max_particles);
}
void repeatedly_execute_always()
{
GPE.UpdateContinuous();
}
// new module header
managed struct ParticleDefinition {
int offsetX; // Offset from the emitter position
int offsetY; // Offset from the emitter position
int life; // Lifetime of the particle
int vx; // mili Velocity in x direction
int vy; // mili Velocity in y direction
int gravity; // mili Gravity effect on the particle
int initialTransparency; // Initial transparency
int finalTransparency; // Final transparency
int initialWidth; // Initial width
int finalWidth; // Final width
int initialHeight; // Initial height
int finalHeight; // Final height
bool groundHitBounces;
int groundY;
int groundX;
int groundWidth;
};
managed struct Particle {
float x;
float y;
int life;
int initialLife;
int overlayIndex; // This refers to the overlay in the overlay pool
float vx; // x velocity
float vy; // y velocity
float gravity; // this is vertical acceleration downwards
int transparency;
int width;
int height;
int initialTransparency;
int finalTransparency;
int initialWidth;
int finalWidth;
int initialHeight;
int finalHeight;
bool bounces;
int groundY;
int groundX;
int groundWidth;
// Initialize the particle with its position, life, velocity, and transparency
import void Init(ParticleDefinition* def, int x, int y, int overlayIndex);
import void Update(); // Update particle position and overlay
import bool IsAlive(); // Check if particle is still alive
};
struct Emitter {
protected int x;
protected int y;
protected int particleLife;
protected int emitParticleCount;
protected int particleCount;
protected int sprite; // The sprite slot to use for particles
protected int gravity;
protected Particle* particles[]; // Pool of particles
protected ParticleDefinition* definitions[]; // Array of particle definitions
protected int definitionsCount; // Count of particle definitions
/// Set sprite
import void SetSprite(int graphic);
/// Set emitter possible particle definitions
import void SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount);
/// Update emitter position
import void SetPosition(int x, int y);
/// Initialize the emitter
import void Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount = 10, int particleCount = 50, int sprite = 0, int gravity = 0);
/// Emit a single particle
import void EmitSingleParticle();
/// Emit particles
import void Emit();
/// Update all particles
import void Update();
};
struct ContinuousEmitter extends Emitter {
protected int emitRate;
protected int _emitRateFrame;
protected bool isEmitting;
import void StartEmitting(int emitRate = 11);
import void StopEmitting();
import void UpdateContinuous();
};
/// Global Particle Emitter
import ContinuousEmitter GPE;
// new module script
#define MAX_OVERLAYS 512
Overlay* overlayPool[MAX_OVERLAYS];
bool overlayUsed[MAX_OVERLAYS];
int lastUsed;
#define MAX_LERP 1024
// lerp from percent 0 to 1024
int _Lerp(int start, int end, int percent) {
if (percent < 0) percent = 0;
if (percent > MAX_LERP) percent = MAX_LERP;
// Calculate the interpolated value
return start + ((end - start) * percent) / MAX_LERP;
}
int InvalidateOverlay(int index)
{
if(overlayPool[index] != null)
overlayPool[index].Remove();
overlayPool[index] = null; // Clear the reference to the overlay
overlayUsed[index] = false; // Mark the overlay slot as free
return -1;
}
// Find an available overlay slot in the pool
function GetAvailableOverlayIndex() {
for (int i = lastUsed; i < MAX_OVERLAYS; i++) {
if (!overlayUsed[i]) {
InvalidateOverlay(i);
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
for (int i = 0; (i < lastUsed) && (i < MAX_OVERLAYS); i++) {
if (!overlayUsed[i]) {
InvalidateOverlay(i);
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
return -1; // No available overlay
}
void UpdateOverlayFromParticle(Overlay* ovr, Particle* p)
{
ovr.X = FloatToInt(p.x);
ovr.Y = FloatToInt(p.y);
ovr.Transparency = p.transparency;
ovr.Width = p.width;
ovr.Height = p.height;
}
void Particle::Init(ParticleDefinition* def, int x, int y, int overlayIndex) {
if(this.overlayIndex >= 0 && this.overlayIndex < MAX_OVERLAYS) {
InvalidateOverlay(this.overlayIndex);
}
this.x = IntToFloat(x + def.offsetX); // Offset based on emitter
this.y = IntToFloat(y + def.offsetY); // Offset based on emitter
this.life = def.life;
this.vx = IntToFloat(def.vx)/1000.0;
this.vy = IntToFloat(def.vy)/1000.0;
this.gravity = IntToFloat(def.gravity)/1000.0;
this.transparency = def.initialTransparency;
this.initialTransparency = def.initialTransparency;
this.finalTransparency = def.finalTransparency;
this.width = def.initialWidth;
this.initialWidth = def.initialWidth;
this.finalWidth = def.finalWidth;
this.height = def.initialHeight;
this.initialHeight = def.initialHeight;
this.finalHeight = def.finalHeight;
this.overlayIndex = overlayIndex;
this.initialLife = def.life; // Store initial life for transitions
this.bounces = def.groundHitBounces;
this.groundY = def.groundY;
this.groundX = def.groundX;
this.groundWidth = def.groundWidth;
if(this.groundY > 0) {
if(this.groundWidth <= 0 ) {
this.groundWidth = 8192;
this.groundX = -1024;
}
} else {
this.groundY = 0;
this.groundX = 0;
this.groundWidth = 0;
}
if (overlayIndex >= 0 && overlayPool[overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[overlayIndex], this);
}
}
bool Particle::IsAlive() {
return (this.life > 0);
}
// Update the particle state and sync with overlay
void Particle::Update() {
if (this.IsAlive()) {
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity; // Apply gravity
this.life--;
int px = FloatToInt(this.x);
int py = FloatToInt(this.y);
// Calculate the scaling and transparency transitions based on life
int lifeRatio = MAX_LERP - ((this.life * MAX_LERP) / this.initialLife); // 0 to 1024
this.transparency = _Lerp(this.initialTransparency, this.finalTransparency, lifeRatio);
this.width = _Lerp(this.initialWidth, this.finalWidth, lifeRatio);
this.height = _Lerp(this.initialHeight, this.finalHeight, lifeRatio);
if (this.overlayIndex >= 0 && overlayPool[this.overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[this.overlayIndex], this);
}
if ((py >= this.groundY) && (px >= this.groundX) && (px <= (this.groundX + this.groundWidth))) {
if (this.bounces) {
this.vy = -this.vy * 0.7; // Invert velocity, reduce it to simulate energy loss
} else {
this.life = 0; // Mark particle as dead
}
}
} else {
// Remove overlay if life is over
if (this.overlayIndex >= 0) {
this.overlayIndex = InvalidateOverlay(this.overlayIndex); // Invalidate the overlay index
}
}
}
void Emitter::SetPosition(int x, int y)
{
this.x = x;
this.y = y;
}
void Emitter::SetSprite(int graphic)
{
this.sprite = graphic;
}
void Emitter::SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount)
{
this.definitions = definitions;
this.definitionsCount = definitionsCount;
}
// Initialize the emitter with position, particle definitions, and specific parameters
void Emitter::Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount, int particleCount, int sprite, int gravity) {
this.SetPosition(x, y);
this.particleCount = particleCount;
this.SetSprite(sprite);
this.gravity = gravity;
this.SetParticleDefinitions(definitions, definitionsCount);
this.emitParticleCount = emitParticleCount;
this.particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++) {
this.particles[i] = new Particle;
}
}
// Emit a single particle from the emitter
void Emitter::EmitSingleParticle() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].IsAlive())
{
continue;
}
// Reuse dead particle if it's not alive anymore
int overlayIndex = GetAvailableOverlayIndex();
if (overlayIndex >= 0) {
Overlay* ovr = Overlay.CreateGraphical(this.x, this.y, this.sprite);
overlayPool[overlayIndex] = ovr;
// Randomly select a particle definition from the available definitions
int defIndex = 0;
if(this.definitionsCount > 0) {
defIndex = Random(this.definitionsCount-1);
}
ParticleDefinition* def = this.definitions[defIndex];
this.particles[i].Init(def, this.x, this.y, overlayIndex);
}
return;
}
}
// Emit particles from the emitter
void Emitter::Emit() {
for (int i = 0; i < this.emitParticleCount; i++) {
this.EmitSingleParticle();
}
}
// Update all particles
void Emitter::Update() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].IsAlive()) {
this.particles[i].Update();
} else if (this.particles[i].overlayIndex >= 0) {
// Ensure overlays are freed for dead particles
this.particles[i].overlayIndex = InvalidateOverlay(this.particles[i].overlayIndex); // Invalidate the overlay index
}
}
}
void ContinuousEmitter::StartEmitting(int emitRate)
{
if(emitRate < 4) emitRate = 4;
this.emitRate = emitRate;
this.isEmitting = true;
}
void ContinuousEmitter::StopEmitting()
{
this.isEmitting = false;
}
void ContinuousEmitter::UpdateContinuous()
{
this.Update();
if(this.isEmitting) {
this._emitRateFrame--;
if(this._emitRateFrame <= 0) {
this._emitRateFrame = this.emitRate;
this.Emit();
}
}
}
ContinuousEmitter GPE;
export GPE;
void game_start()
{
ParticleDefinition* d[];
GPE.Init(Screen.Width/2, Screen.Height/2, d, 0, MAX_OVERLAYS/14, MAX_OVERLAYS, 0, 0);
}
void repeatedly_execute_always()
{
GPE.UpdateContinuous();
}
// room script file
//Emitter emt;
function hGlowingOrb_Look(Hotspot *thisHotspot, CursorMode mode)
{
player.Say("It is the second best glowing orb that I've seen today.");
}
ParticleDefinition* GetFireworksParticle()
{
ParticleDefinition* fireworksParticle = new ParticleDefinition;
fireworksParticle.life = 40;
fireworksParticle.vx = Random(4000) - 2000; // Random outward velocity
fireworksParticle.vy = Random(4000) - 2000;
fireworksParticle.gravity = 0; // No gravity
fireworksParticle.initialTransparency = 0;
fireworksParticle.finalTransparency = 100;
fireworksParticle.initialWidth = 2;
fireworksParticle.finalWidth = 20; // Expanding outward
fireworksParticle.initialHeight = 2;
fireworksParticle.finalHeight = 20;
return fireworksParticle;
}
ParticleDefinition* GetSparkleParticle()
{
ParticleDefinition* sparkleParticle = new ParticleDefinition;
sparkleParticle.life = 50;
sparkleParticle.vx = Random(3000) - 1000;
sparkleParticle.vy = Random(3000) - 1000;
sparkleParticle.initialTransparency = 0;
sparkleParticle.finalTransparency = 100;
sparkleParticle.initialWidth = 3;
sparkleParticle.finalWidth = 8;
sparkleParticle.initialHeight = 3;
sparkleParticle.finalHeight = 8;
sparkleParticle.gravity = 100;
sparkleParticle.groundY = 154;
sparkleParticle.groundHitBounces = true;
return sparkleParticle;
}
ParticleDefinition* GetExplosionParticle()
{
ParticleDefinition* explosionParticle = new ParticleDefinition;
explosionParticle.life = 30;
explosionParticle.vx = Random(6000) - 3000;
explosionParticle.vy = Random(6000) - 3000;
explosionParticle.gravity = -1000;
explosionParticle.initialTransparency = 15;
explosionParticle.finalTransparency = 100;
explosionParticle.initialWidth = 15;
explosionParticle.finalWidth = 30;
explosionParticle.initialHeight = 15;
explosionParticle.finalHeight = 30;
return explosionParticle;
}
ParticleDefinition* GetSmokeParticle()
{
ParticleDefinition* smokeParticle = new ParticleDefinition;
smokeParticle.life = 40+Random(14);
smokeParticle.vy = -1000-Random(1000);
smokeParticle.initialTransparency = 0;
smokeParticle.finalTransparency = 100;
smokeParticle.initialWidth = 10+Random(2);
smokeParticle.finalWidth = 20+Random(2);
smokeParticle.initialHeight = 20+Random(2);
smokeParticle.finalHeight = 10+Random(2);
return smokeParticle;
}
ParticleDefinition* GetBubbleParticle()
{
ParticleDefinition* bubbleParticle = new ParticleDefinition;
bubbleParticle.life = 60;
bubbleParticle.vx = Random(500) - 250; // Small horizontal drift
bubbleParticle.vy = -1000 - Random(500); // Rising upwards
bubbleParticle.gravity = -200; // Rising effect
bubbleParticle.initialTransparency = 30;
bubbleParticle.finalTransparency = 100;
bubbleParticle.initialWidth = 5;
bubbleParticle.finalWidth = 15; // Expands as it rises
bubbleParticle.initialHeight = 5;
bubbleParticle.finalHeight = 15;
return bubbleParticle;
}
ParticleDefinition* GetRainParticle()
{
ParticleDefinition* rainParticle = new ParticleDefinition;
rainParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
rainParticle.offsetY = -Random(30);
rainParticle.life = 50;
rainParticle.vx = Random(500) - 250; // Slight horizontal movement
rainParticle.vy = 3000; // Falling down quickly
rainParticle.gravity = 180; // Light gravity effect
rainParticle.initialTransparency = 30;
rainParticle.finalTransparency = 80;
rainParticle.initialWidth = 2;
rainParticle.finalWidth = 2;
rainParticle.initialHeight = 10;
rainParticle.finalHeight = 15; // Lengthening as it falls
return rainParticle;
}
ParticleDefinition* GetFireParticle()
{
ParticleDefinition* fireParticle = new ParticleDefinition;
fireParticle.life = 35;
fireParticle.vx = Random(1000) - 500; // Small horizontal variance
fireParticle.vy = -1200 - Random(500); // Rising upward
fireParticle.gravity = -50; // Slow upward pull
fireParticle.initialTransparency = 50;
fireParticle.finalTransparency = 100; // Disappears as it rises
fireParticle.initialWidth = 10;
fireParticle.finalWidth = 20; // Expands as it rises
fireParticle.initialHeight = 10;
fireParticle.finalHeight = 15;
return fireParticle;
}
ParticleDefinition* GetSnowParticle()
{
ParticleDefinition* snowParticle = new ParticleDefinition;
snowParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
snowParticle.offsetY = -Random(30);
snowParticle.life = 160;
snowParticle.vx = Random(300) - 150; // Slight horizontal drift
snowParticle.vy = Random(300) + 220; // Slow downward movement
snowParticle.gravity = 10; // Minimal gravity effect
snowParticle.initialTransparency = 50;
snowParticle.finalTransparency = 75;
snowParticle.initialWidth = 4;
snowParticle.finalWidth = 6; // Slight expansion as it falls
snowParticle.initialHeight = 4;
snowParticle.finalHeight = 6;
return snowParticle;
}
enum PresetParticleType {
ePPT_Fireworks,
ePPT_Sparkle,
ePPT_Explosion,
ePPT_Smoke,
ePPT_Bubble,
ePPT_Rain,
ePPT_Fire,
ePPT_Snow
};
#define ePPT_Last ePPT_Snow
ParticleDefinition* [] GetParticleDefinitionsArrayByType(PresetParticleType type, int count)
{
ParticleDefinition* definitions[] = new ParticleDefinition[count];
int i;
switch(type) {
case ePPT_Fireworks:
for(i=0; i<count; i++)
{
definitions[i] = GetFireworksParticle();
}
break;
case ePPT_Sparkle:
for(i=0; i<count; i++)
{
definitions[i] = GetSparkleParticle();
}
break;
case ePPT_Explosion:
for(i=0; i<count; i++)
{
definitions[i] = GetExplosionParticle();
}
break;
case ePPT_Smoke:
for(i=0; i<count; i++)
{
definitions[i] = GetSmokeParticle();
}
break;
case ePPT_Bubble:
for(i=0; i<count; i++)
{
definitions[i] = GetBubbleParticle();
}
break;
case ePPT_Rain:
for(i=0; i<count; i++)
{
definitions[i] = GetRainParticle();
}
break;
case ePPT_Fire:
for(i=0; i<count; i++)
{
definitions[i] = GetFireParticle();
}
break;
case ePPT_Snow:
for(i=0; i<count; i++)
{
definitions[i] = GetSnowParticle();
}
break;
}
return definitions;
}
String GetTypeName(PresetParticleType type) {
switch(type) {
case ePPT_Fireworks:
return "Fireworks";
case ePPT_Sparkle:
return "Sparkle";
case ePPT_Explosion:
return "Explosion";
case ePPT_Smoke:
return "Smoke";
case ePPT_Bubble:
return "Bubble";
case ePPT_Rain:
return "Rain";
case ePPT_Fire:
return "Fire";
case ePPT_Snow:
return "Snow";
default:
return "Unknown";
}
}
void SetEmitterToType(PresetParticleType type)
{
int definitions_count = 2048;
ParticleDefinition* definitions[] = GetParticleDefinitionsArrayByType(type, definitions_count);
GPE.SetParticleDefinitions(definitions, definitions_count);
lbl_particle_selected.Text = GetTypeName(type);
}
int particle_type;
void on_call (int value)
{
if(value == 1) {
particle_type++;
if(particle_type> ePPT_Last) {
particle_type = 1;
}
}
SetEmitterToType(particle_type);
if(particle_type == ePPT_Rain || particle_type == ePPT_Snow) {
GPE.SetPosition(Screen.Width/2, 0);
GPE.StartEmitting();
} else {
GPE.StopEmitting();
}
}
function room_Load()
{
SetEmitterToType(ePPT_Fireworks);
}
void on_mouse_click(MouseButton button)
{
if(particle_type == ePPT_Rain || particle_type == ePPT_Snow)
return;
GPE.SetPosition(mouse.x, mouse.y);
GPE.Emit();
}
int mb_press;
function room_RepExec()
{
if(particle_type == ePPT_Rain || particle_type == ePPT_Snow)
return;
if(mouse.IsButtonDown(eMouseLeft))
mb_press++;
else
mb_press = 0;
if(mb_press > 10) {
GPE.SetPosition(mouse.x, mouse.y);
GPE.StartEmitting(5);
} else {
GPE.StopEmitting();
}
}
// new module header
managed struct ParticleDefinition {
int offsetX; // Offset from the emitter position
int offsetY; // Offset from the emitter position
int life; // Lifetime of the particle
int vx; // mili Velocity in x direction
int vy; // mili Velocity in y direction
int gravity; // mili Gravity effect on the particle
int initialTransparency; // Initial transparency
int finalTransparency; // Final transparency
int initialWidth; // Initial width
int finalWidth; // Final width
int initialHeight; // Initial height
int finalHeight; // Final height
bool groundHitBounces;
int groundY;
int groundX;
int groundWidth;
};
managed struct Particle {
float x;
float y;
int life;
int initialLife;
int overlayIndex; // This refers to the overlay in the overlay pool
float vx; // x velocity
float vy; // y velocity
float gravity; // this is vertical acceleration downwards
int transparency;
int width;
int height;
int initialTransparency;
int finalTransparency;
int initialWidth;
int finalWidth;
int initialHeight;
int finalHeight;
bool bounces;
int groundY;
int groundX;
int groundWidth;
// Initialize the particle with its position, life, velocity, and transparency
import void Init(ParticleDefinition* def, int x, int y, int overlayIndex);
import void Update(); // Update particle position and overlay
import bool IsAlive(); // Check if particle is still alive
};
struct Emitter {
protected int x;
protected int y;
protected int particleLife;
protected int emitParticleCount;
protected int particleCount;
protected int sprite; // The sprite slot to use for particles
protected int gravity;
protected Particle* particles[]; // Pool of particles
protected ParticleDefinition* definitions[]; // Array of particle definitions
protected int definitionsCount; // Count of particle definitions
/// Set sprite
import void SetSprite(int graphic);
/// Set emitter possible particle definitions
import void SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount);
/// Update emitter position
import void SetPosition(int x, int y);
/// Initialize the emitter
import void Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount = 10, int particleCount = 50, int sprite = 0, int gravity = 0);
/// Emit a single particle
import void EmitSingleParticle();
/// Emit particles
import void Emit();
/// Update all particles
import void Update();
};
/// Global Particle Emitter
import Emitter GPE;
// new module script
#define MAX_OVERLAYS 512
Overlay* overlayPool[MAX_OVERLAYS];
bool overlayUsed[MAX_OVERLAYS];
int lastUsed;
Emitter GPE;
export GPE;
#define MAX_LERP 1024
// lerp from percent 0 to 1024
int _Lerp(int start, int end, int percent) {
if (percent < 0) percent = 0;
if (percent > MAX_LERP) percent = MAX_LERP;
// Calculate the interpolated value
return start + ((end - start) * percent) / MAX_LERP;
}
int InvalidateOverlay(int index)
{
if(overlayPool[index] != null)
overlayPool[index].Remove();
overlayPool[index] = null; // Clear the reference to the overlay
overlayUsed[index] = false; // Mark the overlay slot as free
return -1;
}
// Find an available overlay slot in the pool
function GetAvailableOverlayIndex() {
for (int i = lastUsed; i < MAX_OVERLAYS; i++) {
if (!overlayUsed[i]) {
InvalidateOverlay(i);
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
for (int i = 0; (i < lastUsed) && (i < MAX_OVERLAYS); i++) {
if (!overlayUsed[i]) {
InvalidateOverlay(i);
overlayUsed[i] = true;
lastUsed = i;
return i;
}
}
return -1; // No available overlay
}
void UpdateOverlayFromParticle(Overlay* ovr, Particle* p)
{
ovr.X = FloatToInt(p.x);
ovr.Y = FloatToInt(p.y);
ovr.Transparency = p.transparency;
ovr.Width = p.width;
ovr.Height = p.height;
}
void Particle::Init(ParticleDefinition* def, int x, int y, int overlayIndex) {
if(this.overlayIndex >= 0 && this.overlayIndex < MAX_OVERLAYS) {
InvalidateOverlay(this.overlayIndex);
}
this.x = IntToFloat(x + def.offsetX); // Offset based on emitter
this.y = IntToFloat(y + def.offsetY); // Offset based on emitter
this.life = def.life;
this.vx = IntToFloat(def.vx)/1000.0;
this.vy = IntToFloat(def.vy)/1000.0;
this.gravity = IntToFloat(def.gravity)/1000.0;
this.transparency = def.initialTransparency;
this.initialTransparency = def.initialTransparency;
this.finalTransparency = def.finalTransparency;
this.width = def.initialWidth;
this.initialWidth = def.initialWidth;
this.finalWidth = def.finalWidth;
this.height = def.initialHeight;
this.initialHeight = def.initialHeight;
this.finalHeight = def.finalHeight;
this.overlayIndex = overlayIndex;
this.initialLife = def.life; // Store initial life for transitions
this.bounces = def.groundHitBounces;
this.groundY = def.groundY;
this.groundX = def.groundX;
this.groundWidth = def.groundWidth;
if(this.groundY > 0) {
if(this.groundWidth <= 0 ) {
this.groundWidth = 8192;
this.groundX = -1024;
}
} else {
this.groundY = 0;
this.groundX = 0;
this.groundWidth = 0;
}
if (overlayIndex >= 0 && overlayPool[overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[overlayIndex], this);
}
}
bool Particle::IsAlive() {
return (this.life > 0);
}
// Update the particle state and sync with overlay
void Particle::Update() {
if (this.IsAlive()) {
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity; // Apply gravity
this.life--;
int px = FloatToInt(this.x);
int py = FloatToInt(this.y);
// Calculate the scaling and transparency transitions based on life
int lifeRatio = MAX_LERP - ((this.life * MAX_LERP) / this.initialLife); // 0 to 1024
this.transparency = _Lerp(this.initialTransparency, this.finalTransparency, lifeRatio);
this.width = _Lerp(this.initialWidth, this.finalWidth, lifeRatio);
this.height = _Lerp(this.initialHeight, this.finalHeight, lifeRatio);
if (this.overlayIndex >= 0 && overlayPool[this.overlayIndex] != null) {
UpdateOverlayFromParticle(overlayPool[this.overlayIndex], this);
}
if ((py >= this.groundY) && (px >= this.groundX) && (px <= (this.groundX + this.groundWidth))) {
if (this.bounces) {
this.vy = -this.vy * 0.7; // Invert velocity, reduce it to simulate energy loss
} else {
this.life = 0; // Mark particle as dead
}
}
} else {
// Remove overlay if life is over
if (this.overlayIndex >= 0) {
this.overlayIndex = InvalidateOverlay(this.overlayIndex); // Invalidate the overlay index
}
}
}
void Emitter::SetPosition(int x, int y)
{
this.x = x;
this.y = y;
}
void Emitter::SetSprite(int graphic)
{
this.sprite = graphic;
}
void Emitter::SetParticleDefinitions(ParticleDefinition* definitions[], int definitionsCount)
{
this.definitions = definitions;
this.definitionsCount = definitionsCount;
}
// Initialize the emitter with position, particle definitions, and specific parameters
void Emitter::Init(int x, int y, ParticleDefinition* definitions[], int definitionsCount, int emitParticleCount, int particleCount, int sprite, int gravity) {
this.SetPosition(x, y);
this.particleCount = particleCount;
this.SetSprite(sprite);
this.gravity = gravity;
this.SetParticleDefinitions(definitions, definitionsCount);
this.emitParticleCount = emitParticleCount;
this.particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++) {
this.particles[i] = new Particle;
}
}
// Emit a single particle from the emitter
void Emitter::EmitSingleParticle() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].IsAlive())
{
continue;
}
// Reuse dead particle if it's not alive anymore
int overlayIndex = GetAvailableOverlayIndex();
if (overlayIndex >= 0) {
Overlay* ovr = Overlay.CreateGraphical(this.x, this.y, this.sprite);
overlayPool[overlayIndex] = ovr;
// Randomly select a particle definition from the available definitions
int defIndex = 0;
if(this.definitionsCount > 0) {
defIndex = Random(this.definitionsCount-1);
}
ParticleDefinition* def = this.definitions[defIndex];
this.particles[i].Init(def, this.x, this.y, overlayIndex);
}
return;
}
}
// Emit particles from the emitter
void Emitter::Emit() {
for (int i = 0; i < this.emitParticleCount; i++) {
this.EmitSingleParticle();
}
}
// Update all particles
void Emitter::Update() {
for (int i = 0; i < this.particleCount; i++) {
if (this.particles[i].IsAlive()) {
this.particles[i].Update();
} else if (this.particles[i].overlayIndex >= 0) {
// Ensure overlays are freed for dead particles
this.particles[i].overlayIndex = InvalidateOverlay(this.particles[i].overlayIndex); // Invalidate the overlay index
}
}
}
void game_start()
{
ParticleDefinition* d[];
GPE.Init(Screen.Width/2, Screen.Height/2, d, 0, MAX_OVERLAYS/14, MAX_OVERLAYS/2, 0, 0);
}
void repeatedly_execute_always()
{
GPE.Update();
}
// room script file
//Emitter emt;
function hGlowingOrb_Look(Hotspot *thisHotspot, CursorMode mode)
{
player.Say("It is the second best glowing orb that I've seen today.");
}
ParticleDefinition* GetFireworksParticle()
{
ParticleDefinition* fireworksParticle = new ParticleDefinition;
fireworksParticle.life = 40;
fireworksParticle.vx = Random(4000) - 2000; // Random outward velocity
fireworksParticle.vy = Random(4000) - 2000;
fireworksParticle.gravity = 0; // No gravity
fireworksParticle.initialTransparency = 0;
fireworksParticle.finalTransparency = 100;
fireworksParticle.initialWidth = 2;
fireworksParticle.finalWidth = 20; // Expanding outward
fireworksParticle.initialHeight = 2;
fireworksParticle.finalHeight = 20;
return fireworksParticle;
}
ParticleDefinition* GetSparkleParticle()
{
ParticleDefinition* sparkleParticle = new ParticleDefinition;
sparkleParticle.life = 50;
sparkleParticle.vx = Random(3000) - 1000;
sparkleParticle.vy = Random(3000) - 1000;
sparkleParticle.initialTransparency = 0;
sparkleParticle.finalTransparency = 100;
sparkleParticle.initialWidth = 3;
sparkleParticle.finalWidth = 8;
sparkleParticle.initialHeight = 3;
sparkleParticle.finalHeight = 8;
sparkleParticle.gravity = 100;
sparkleParticle.groundY = 154;
sparkleParticle.groundHitBounces = true;
return sparkleParticle;
}
ParticleDefinition* GetExplosionParticle()
{
ParticleDefinition* explosionParticle = new ParticleDefinition;
explosionParticle.life = 30;
explosionParticle.vx = Random(6000) - 3000;
explosionParticle.vy = Random(6000) - 3000;
explosionParticle.gravity = -1000;
explosionParticle.initialTransparency = 15;
explosionParticle.finalTransparency = 100;
explosionParticle.initialWidth = 15;
explosionParticle.finalWidth = 30;
explosionParticle.initialHeight = 15;
explosionParticle.finalHeight = 30;
return explosionParticle;
}
ParticleDefinition* GetSmokeParticle()
{
ParticleDefinition* smokeParticle = new ParticleDefinition;
smokeParticle.life = 40+Random(14);
smokeParticle.vy = -1000-Random(1000);
smokeParticle.initialTransparency = 0;
smokeParticle.finalTransparency = 100;
smokeParticle.initialWidth = 10+Random(2);
smokeParticle.finalWidth = 20+Random(2);
smokeParticle.initialHeight = 20+Random(2);
smokeParticle.finalHeight = 10+Random(2);
return smokeParticle;
}
ParticleDefinition* GetBubbleParticle()
{
ParticleDefinition* bubbleParticle = new ParticleDefinition;
bubbleParticle.life = 60;
bubbleParticle.vx = Random(500) - 250; // Small horizontal drift
bubbleParticle.vy = -1000 - Random(500); // Rising upwards
bubbleParticle.gravity = -200; // Rising effect
bubbleParticle.initialTransparency = 30;
bubbleParticle.finalTransparency = 100;
bubbleParticle.initialWidth = 5;
bubbleParticle.finalWidth = 15; // Expands as it rises
bubbleParticle.initialHeight = 5;
bubbleParticle.finalHeight = 15;
return bubbleParticle;
}
ParticleDefinition* GetRainParticle()
{
ParticleDefinition* rainParticle = new ParticleDefinition;
rainParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
rainParticle.offsetY = -Random(30);
rainParticle.life = 50;
rainParticle.vx = Random(500) - 250; // Slight horizontal movement
rainParticle.vy = 3000; // Falling down quickly
rainParticle.gravity = 200; // Light gravity effect
rainParticle.initialTransparency = 30;
rainParticle.finalTransparency = 80;
rainParticle.initialWidth = 2;
rainParticle.finalWidth = 2;
rainParticle.initialHeight = 10;
rainParticle.finalHeight = 15; // Lengthening as it falls
return rainParticle;
}
ParticleDefinition* GetFireParticle()
{
ParticleDefinition* fireParticle = new ParticleDefinition;
fireParticle.life = 35;
fireParticle.vx = Random(1000) - 500; // Small horizontal variance
fireParticle.vy = -1500 - Random(500); // Rising upward
fireParticle.gravity = -50; // Slow upward pull
fireParticle.initialTransparency = 50;
fireParticle.finalTransparency = 100; // Disappears as it rises
fireParticle.initialWidth = 10;
fireParticle.finalWidth = 20; // Expands as it rises
fireParticle.initialHeight = 10;
fireParticle.finalHeight = 15;
return fireParticle;
}
ParticleDefinition* GetSnowParticle()
{
ParticleDefinition* snowParticle = new ParticleDefinition;
snowParticle.offsetX = Random(Screen.Width) - (Screen.Width/2);
snowParticle.offsetY = -Random(30);
snowParticle.life = 150;
snowParticle.vx = Random(300) - 150; // Slight horizontal drift
snowParticle.vy = Random(300) + 300; // Slow downward movement
snowParticle.gravity = 15; // Minimal gravity effect
snowParticle.initialTransparency = 50;
snowParticle.finalTransparency = 80;
snowParticle.initialWidth = 4;
snowParticle.finalWidth = 6; // Slight expansion as it falls
snowParticle.initialHeight = 4;
snowParticle.finalHeight = 6;
return snowParticle;
}
enum PresetParticleType {
ePPT_Fireworks,
ePPT_Sparkle,
ePPT_Explosion,
ePPT_Smoke,
ePPT_Bubble,
ePPT_Rain,
ePPT_Fire,
ePPT_Snow
};
#define ePPT_Last ePPT_Snow
ParticleDefinition* [] GetParticleDefinitionsArrayByType(PresetParticleType type, int count)
{
ParticleDefinition* definitions[] = new ParticleDefinition[count];
int i;
switch(type) {
case ePPT_Fireworks:
for(i=0; i<count; i++)
{
definitions[i] = GetFireworksParticle();
}
break;
case ePPT_Sparkle:
for(i=0; i<count; i++)
{
definitions[i] = GetSparkleParticle();
}
break;
case ePPT_Explosion:
for(i=0; i<count; i++)
{
definitions[i] = GetExplosionParticle();
}
break;
case ePPT_Smoke:
for(i=0; i<count; i++)
{
definitions[i] = GetSmokeParticle();
}
break;
case ePPT_Bubble:
for(i=0; i<count; i++)
{
definitions[i] = GetBubbleParticle();
}
break;
case ePPT_Rain:
for(i=0; i<count; i++)
{
definitions[i] = GetRainParticle();
}
break;
case ePPT_Fire:
for(i=0; i<count; i++)
{
definitions[i] = GetFireParticle();
}
break;
case ePPT_Snow:
for(i=0; i<count; i++)
{
definitions[i] = GetSnowParticle();
}
break;
}
return definitions;
}
String GetTypeName(PresetParticleType type) {
switch(type) {
case ePPT_Fireworks:
return "Fireworks";
case ePPT_Sparkle:
return "Sparkle";
case ePPT_Explosion:
return "Explosion";
case ePPT_Smoke:
return "Smoke";
case ePPT_Bubble:
return "Bubble";
case ePPT_Rain:
return "Rain";
case ePPT_Fire:
return "Fire";
case ePPT_Snow:
return "Snow";
default:
return "Unknown";
}
}
void SetEmitterToType(PresetParticleType type)
{
int definitions_count = 2048;
ParticleDefinition* definitions[] = GetParticleDefinitionsArrayByType(type, definitions_count);
GPE.SetParticleDefinitions(definitions, definitions_count);
lbl_particle_selected.Text = GetTypeName(type);
}
int particle_type;
void on_call (int value)
{
if(value == 1) {
particle_type++;
if(particle_type> ePPT_Last) {
particle_type = 1;
}
}
SetEmitterToType(particle_type);
}
function room_Load()
{
SetEmitterToType(ePPT_Fireworks);
}
void on_mouse_click(MouseButton button)
{
int mx = mouse.x;
int my = mouse.y;
GPE.SetPosition(mx, my);
GPE.Emit();
}
By continuing to use this site you agree to the use of cookies. Please visit this page to see exactly how we use these.
Page created in 0.181 seconds with 15 queries.