Esempio n. 1
0
TEST_F(ParticleTest, InterpolationMulti) {
	// Read Fields
	unsigned int size = 0;
	double *uext = ReadArray("../test/data/uext16-real.dat", &size);
	double *vext = ReadArray("../test/data/vext16-real.dat", &size);
	double *wext = ReadArray("../test/data/wext16-real.dat", &size);
	double *text = ReadArray("../test/data/text16-real.dat", &size);
	double *qext = ReadArray("../test/data/qext16-real.dat", &size);
	double *Z = ReadArray("../test/data/Z16-real.dat", &size);
	double *ZZ = ReadArray("../test/data/ZZ16-real.dat", &size);

	// Create GPU
	params.LinearInterpolation = 0;
	GPU *gpu = NewGPU(2, 21, 21, 18, 0.5, 1.0, 0.0, Z, ZZ, &params);

	// Setup Variables
	double xl = 0.251327, yl = 0.251327;
	double dx = xl / 16.0, dy = yl / 16.0;

	// Setup Particle
	Particle input2 = {
		2, 0, {0.0, 0.0, 0.0}, {2 * dx, 2 * dx, 0.021}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	ParticleAdd(gpu, 1, &input2);

	Particle input = {
		1, 0, {0.0, 0.0, 0.0}, {dx, dy, 0.021}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	ParticleAdd(gpu, 0, &input);

	// Update Particle
	ParticleUpload(gpu);
	ParticleFieldSet(gpu, uext, vext, wext, text, qext);
	ParticleInterpolate(gpu, dx, dy);
	ParticleDownload(gpu);

	// Compare Results
	GPU *expected = ParticleRead("../test/data/InterpolationMulti.dat");
	ASSERT_EQ(gpu->pCount, expected->pCount);

	for(int i = 0; i < gpu->pCount; i++) {
		for(int j = 0; j < gpu->pCount; j++) {
			if(gpu->hParticles[i].pidx == expected->hParticles[j].pidx) {
				CompareParticle(&gpu->hParticles[i], &expected->hParticles[j]);
			}
		}
	}

	// Free Data
	free(gpu);
}
Esempio n. 2
0
TEST_F(ParticleTest, InterpolationSecond) {
	// Read Fields
	unsigned int size = 0;
	double *uext = ReadArray("../test/data/uext_128.dat", &size);
	double *vext = ReadArray("../test/data/vext_128.dat", &size);
	double *wext = ReadArray("../test/data/wext_128.dat", &size);
	double *text = ReadArray("../test/data/text_128.dat", &size);
	double *qext = ReadArray("../test/data/qext_128.dat", &size);
	double *Z = ReadArray("../test/data/z_128.dat", &size);
	double *ZZ = ReadArray("../test/data/zz_128.dat", &size);

	// Create GPU
	params.LinearInterpolation = 1;
	GPU *gpu = NewGPU(1, 133, 133, 130, 0.251327, 0.125664, 0.040000, Z, ZZ, &params);

	// Setup Variables
	double dx = 0.0019634921875, dy = 0.00098175;

	// Setup Particle
	GPU *input = ParticleRead("../test/data/InterpolationSecondInput.dat");
	ParticleAdd(gpu, 0, &input->hParticles[0]);

	// Update Particle
	ParticleUpload(gpu);
	ParticleFieldSet(gpu, uext, vext, wext, text, qext);
	ParticleInterpolate(gpu, dx, dy);
	ParticleDownload(gpu);

	// Compare Results
	GPU *expected = ParticleRead("../test/data/InterpolationSecondExpected.dat");
	CompareParticle(&gpu->hParticles[0], &expected->hParticles[0]);

	// Free Data
	free(gpu);
}
Esempio n. 3
0
TEST_F(ParticleTest, StatisticCountEveryOther) {
	// Read Fields
	unsigned int size = 0;
	double *uext = ReadArray("../test/data/uext.dat", &size);
	double *vext = ReadArray("../test/data/vext.dat", &size);
	double *wext = ReadArray("../test/data/wext.dat", &size);
	double *text = ReadArray("../test/data/text.dat", &size);
	double *qext = ReadArray("../test/data/qext.dat", &size);
	double *Z = ReadArray("../test/data/Z.dat", &size);
	double *ZZ = ReadArray("../test/data/ZZ.dat", &size);

	// Create GPU
	GPU *gpu = NewGPU(8, 11, 11, 8, 0.5, 1.0, 0.0, Z, ZZ, &params);

	// Setup Variables
	double xl = 0.251327, yl = 0.251327;
	double dx = xl / 6.0, dy = yl / 6.0;

	// Setup Particle
	int j = 0;
	for(int i = 0; i < size; i++) {
		if(i != 0 && i % 2 == 0) {
			j += 2;
		}
		Particle p = {0, 0, {0.0, 0.0, 0.0}, {xl / 2.0, yl / 2.0, Z[j]}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
		ParticleAdd(gpu, i, &p);
	}

	// Update Particle
	ParticleUpload(gpu);
	ParticleFieldSet(gpu, uext, vext, wext, text, qext);
	ParticleCalculateStatistics(gpu, dx, dy);

	// Compare Results
	for(int i = 0; i < size; i++) {
		if(i % 2 == 0) {
			ASSERT_EQ(gpu->hPartCount[i], 2);
		} else {
			ASSERT_EQ(gpu->hPartCount[i], 0);
		}
	}

	// Free Data
	free(gpu);
}
Esempio n. 4
0
TEST_F(ParticleTest, StatisticVPSum) {
	// Read Fields
	unsigned int size = 0;
	double *uext = ReadArray("../test/data/uext.dat", &size);
	double *vext = ReadArray("../test/data/vext.dat", &size);
	double *wext = ReadArray("../test/data/wext.dat", &size);
	double *text = ReadArray("../test/data/text.dat", &size);
	double *qext = ReadArray("../test/data/qext.dat", &size);
	double *Z = ReadArray("../test/data/Z.dat", &size);
	double *ZZ = ReadArray("../test/data/ZZ.dat", &size);

	// Create GPU
	GPU *gpu = NewGPU(8, 11, 11, 8, 0.5, 1.0, 0.0, Z, ZZ, &params);

	// Setup Variables
	double xl = 0.251327, yl = 0.251327;
	double dx = xl / 6.0, dy = yl / 6.0;

	// Setup Particle
	for(int i = 0; i < size; i++) {
		Particle p = {0, 0, {1.0, 0.0, 0.0}, {xl / 2.0, yl / 2.0, Z[i]}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
		ParticleAdd(gpu, i, &p);
	}

	// Update Particle
	ParticleUpload(gpu);
	ParticleFieldSet(gpu, uext, vext, wext, text, qext);
	ParticleCalculateStatistics(gpu, dx, dy);

	// Compare Results
	double expected[] = {1.0, 0.0, 0.0};
	for(int i = 0; i < size; i++) {
		ASSERT_EQ(gpu->hVPSum[i * 3 + 0], expected[0]);
	}

	// Free Data
	free(gpu);
}
Esempio n. 5
0
// This test should test that the particle is negatively out of bounds
// horizontally and positively vertically and should set its X position
// to Width+X and Y position to Y-Height
TEST_F(ParticleTest, PeriodicPositiveXNegativeY) {
	// Create GPU
	double z[1], zz[1];
	GPU *gpu = NewGPU(1, 0, 0, 0, 0.5, 1.0, 0.0, &z[0], &zz[0], &params);

	// Setup Particle
	Particle input = {
		0, 0, {0.0, 0.0, 0.0}, {0.75, -0.5, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	ParticleAdd(gpu, 0, &input);

	// Update Particle
	ParticleUpload(gpu);
	ParticleUpdatePeriodic(gpu);
	ParticleDownload(gpu);

	// Compare Results
	Particle expected = {
		0, 0, {0.0, 0.0, 0.0}, {0.25, 0.5, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	CompareParticle(&gpu->hParticles[0], &expected);

	// Free Data
	free(gpu);
}
Esempio n. 6
0
TEST_F(ParticleTest, InterpolationZELSE16) {
	// Read Fields
	unsigned int size = 0;
	double *uext = ReadArray("../test/data/uext16.dat", &size);
	double *vext = ReadArray("../test/data/vext16.dat", &size);
	double *wext = ReadArray("../test/data/wext16.dat", &size);
	double *text = ReadArray("../test/data/text16.dat", &size);
	double *qext = ReadArray("../test/data/qext16.dat", &size);
	double *Z = ReadArray("../test/data/Z16.dat", &size);
	double *ZZ = ReadArray("../test/data/ZZ16.dat", &size);

	// Create GPU
	params.LinearInterpolation = 0;
	GPU *gpu = NewGPU(1, 21, 21, 18, 0.5, 1.0, 0.0, Z, ZZ, &params);

	// Setup Variables
	double xl = 0.251327, yl = 0.251327;
	double dx = xl / 16.0, dy = yl / 16.0;

	// Setup Particle
	Particle input = {
		0, 0, {0.0, 0.0, 0.0}, {xl / 16.0, yl / 16.0, 0.021}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	ParticleAdd(gpu, 0, &input);

	// Update Particle
	ParticleUpload(gpu);
	ParticleFieldSet(gpu, uext, vext, wext, text, qext);
	ParticleInterpolate(gpu, dx, dy);
	ParticleDownload(gpu);

	// Compare Results
	GPU *expected = ParticleRead("../test/data/InterpolationZELSE16.dat");
	CompareParticle(&gpu->hParticles[0], &expected->hParticles[0]);

	// Free Data
	free(gpu);
}
Esempio n. 7
0
Leader::Leader(float x, float y, int N, std::string whichPattern, bool yestofollowers, bool tracersYorN, bool slowmo) {
  // Some constants
  displayx = x;
  displayy = y;
  numberofleaders = N;
  conv = 3.141592 / 180.0; 
  centerMass = 500.0;
  center = sf::Vector2f(displayx/2.0, displayy/2.0);
  radius = 2.0;
  pattern = whichPattern;
  putinFollowers = yestofollowers;
  tracersONorOFF = tracersYorN;
  slowdown = slowmo;
  if( slowdown ) {
    centerMass = 100;
  }
  
  // Initialize leader properties
  circles.setRadius(radius);
  circles.setFillColor(sf::Color::Cyan);
  sf::FloatRect origin = circles.getLocalBounds();
  circles.setOrigin( 0.5*origin.width, 0.5*origin.height );
  
  // Tracers
  R_knot = 500;
  if( putinFollowers && tracersONorOFF ) {
    m_lifetime = sf::Time(sf::seconds(1));
  }
  else{
    m_lifetime = sf::Time(sf::seconds(10));
  }
  tracertemplate.setRadius(radius);
  tracertemplate.setFillColor(sf::Color::Cyan);
  origin = tracertemplate.getLocalBounds();
  tracertemplate.setOrigin( 0.5*origin.width, 0.5*origin.height );

  tracertemplateF.setRadius(radius);
  tracertemplateF.setFillColor(sf::Color::Blue);
  origin = tracertemplateF.getLocalBounds();
  tracertemplateF.setOrigin( 0.5*origin.width, 0.5*origin.height );

  srand(time(NULL));
  
  // This routine handles starting locations, velocities, and angles
  // for a variety of setups.
  if( pattern    == "spiral"        || pattern == "circle" 
      || pattern == "flower"        || pattern == "flower_spiral" 
      || pattern == "double circle" || pattern == "puzzle piece center" 
      || pattern == "bulls eye"     || pattern == "crazy folds" ){
    for( int i=0; i<numberofleaders; i++ ){
      
      float tempAngle = 360 / numberofleaders;
    
      sf::Vector2f tempPosition( -R_knot*sin( conv*tempAngle*i ), R_knot*cos( conv*tempAngle*i ) );
      sf::Vector2f place = center + tempPosition;
      circles.setPosition(place);    

      sf::Vector2f centertoleader = place - center;
      float mag = sqrt(pow(centertoleader.x,2)+pow(centertoleader.y,2));
      sf::Vector2f center_hat = centertoleader / mag;

      // Counter Clockwise
      sf::Vector2f vel_hat( center_hat.y, -center_hat.x );
      // Clockwise
      sf::Vector2f vel_hat1( -center_hat.y, center_hat.x );
      float vx,vy;

      if( pattern == "puzzle piece center" ) {
	centerMass = 10000;
      }      
      if( pattern == "flower" || pattern == "flower_spiral" || pattern == "bulls eye" ) { 
	if( pattern == "bulls eye" ) {
	  centerMass = 10000;
	}
	vx = sqrt(centerMass / R_knot );
	vy = sqrt(centerMass / R_knot );
      }
      if (pattern == "crazy folds" ){
	vx = 10;
	vy = 0.5;
      }
      else{
	// note: 16 and 16 basically give  a perfect circle
	vx = 10;
	vy = 10;
      }
     
      sf::Vector2f velocity_knot( vel_hat.x*vx, vel_hat.y*vy );
      float m = 10;
      ParticleAdd(circles, velocity_knot, tempAngle, m);
      
      if( pattern == "double circle" ) {
	float tempR = R_knot / 20;
	tempPosition = sf::Vector2f( -tempR*sin( conv*tempAngle*i ), tempR*cos( conv*tempAngle*i ) );
	place = center + tempPosition;
	circles.setPosition(place);  
	centertoleader = place - center;
	mag = sqrt(pow(centertoleader.x,2)+pow(centertoleader.y,2));
	center_hat = centertoleader / mag;
	vel_hat = sf::Vector2f( center_hat.y, -center_hat.x );
	
	vx = 10;
	vy = 10;
	
	velocity_knot = sf::Vector2f( vel_hat.x*vx, vel_hat.y*vy );
       	ParticleAdd(circles, velocity_knot, tempAngle, m);
      }
    }
    
    if( pattern == "random" ){
      // Randomly place the leaders with random velocity vectors:
      float tempX = rand() % ( int (displayx) - 2*int(radius) ) + int(radius);
      float tempY = rand() % ( int (displayy) - 2*int(radius) ) + int(radius);
      float tempAngle = rand() % 360;;  
      circles.setPosition( tempX, tempY );
      float vx = (rand() % 40) / 10.0;
      float vy = (rand() % 40) / 10.0;
      sf::Vector2f velocity_knot( -vx*sin( conv*tempAngle ), vy*cos( conv*tempAngle) );   
      float m = 10;
      ParticleAdd(circles, velocity_knot, tempAngle, m);
    }
  }
  if(    pattern == "boxTL"       || pattern == "boxC" 
      || pattern == "boxTL folds" || pattern == "boxC folds" ){
    
    numberofleaders = 10000;
    // squareD might need to be tweaked
    float squareD = center.y - 100;
    sf::Vector2f topLeft = center - sf::Vector2f(squareD,squareD);
    int particles_per_row = 0; 
    if( numberofleaders % 100 == 0) {
      particles_per_row = sqrt( numberofleaders );
    }
    else {
      std::cerr << "error, check box routine" << std::endl;
    }
    float spacing = squareD / particles_per_row;
    // i = rows
    // j = columns
    for( int i=0; i<particles_per_row; i++ ) {
      for( int j=0; j<particles_per_row; j++ ) {
	sf::Vector2f temp(0,0);
		
	if( pattern == "boxC" || pattern == "boxC folds" ) {
	  temp = topLeft + sf::Vector2f(i*2*spacing,j*2*spacing);
	}
	
	if( pattern == "boxTL" || pattern == "boxTL folds" ) {
	  temp = topLeft + sf::Vector2f(i*spacing,j*spacing);
	}
	
	circles.setPosition( temp );

	sf::Vector2f velocity_knot(0,0);
	if( pattern == "boxTL folds" || pattern == "boxC folds" ) { 
	  velocity_knot = sf::Vector2f( 3,3 );
	}
	
	float m = 10;
	ParticleAdd(circles,velocity_knot,0.0,m);
      }
    }
  }
  if( putinFollowers ) {
    for( int i=0; i<numberofleaders; i++ ){
      // Randomly place the followers with random velocity vectors:
      float tempX = rand() % ( int (displayx) - 2*int(radius) ) + int(radius);
      float tempY = rand() % ( int (displayy) - 2*int(radius) ) + int(radius);
      circles.setPosition( tempX, tempY );
      circles.setFillColor( sf::Color::Blue );
      float tempAngle = rand() % 360;
    
      float vx = (rand() % 90) / 10.0;
      float vy = (rand() % 90) / 10.0;
      sf::Vector2f velocity_knot( -vx*sin( conv*tempAngle ), vy*cos( conv*tempAngle) );
      FollowerAdd(circles, velocity_knot, tempAngle);
    }
  }
}
Esempio n. 8
0
static void HandleGameEvent(
	const GameEvent e,
	Camera *camera,
	PowerupSpawner *healthSpawner,
	CArray *ammoSpawners)
{
	switch (e.Type)
	{
	case GAME_EVENT_PLAYER_DATA:
		PlayerDataAddOrUpdate(e.u.PlayerData);
		break;
	case GAME_EVENT_TILE_SET:
		{
			Tile *t = MapGetTile(&gMap, Net2Vec2i(e.u.TileSet.Pos));
			t->flags = e.u.TileSet.Flags;
			t->pic = PicManagerGetNamedPic(
				&gPicManager, e.u.TileSet.PicName);
			t->picAlt = PicManagerGetNamedPic(
				&gPicManager, e.u.TileSet.PicAltName);
		}
		break;
	case GAME_EVENT_MAP_OBJECT_ADD:
		ObjAdd(e.u.MapObjectAdd);
		break;
	case GAME_EVENT_MAP_OBJECT_DAMAGE:
		DamageObject(e.u.MapObjectDamage);
		break;
	case GAME_EVENT_SCORE:
		{
			PlayerData *p = PlayerDataGetByUID(e.u.Score.PlayerUID);
			PlayerScore(p, e.u.Score.Score);
			HUDAddUpdate(
				&camera->HUD,
				NUMBER_UPDATE_SCORE, e.u.Score.PlayerUID, e.u.Score.Score);
		}
		break;
	case GAME_EVENT_SOUND_AT:
		if (!e.u.SoundAt.IsHit || ConfigGetBool(&gConfig, "Sound.Hits"))
		{
			SoundPlayAt(
				&gSoundDevice,
				StrSound(e.u.SoundAt.Sound), Net2Vec2i(e.u.SoundAt.Pos));
		}
		break;
	case GAME_EVENT_SCREEN_SHAKE:
		camera->shake = ScreenShakeAdd(
			camera->shake, e.u.ShakeAmount,
			ConfigGetInt(&gConfig, "Graphics.ShakeMultiplier"));
		break;
	case GAME_EVENT_SET_MESSAGE:
		HUDDisplayMessage(
			&camera->HUD, e.u.SetMessage.Message, e.u.SetMessage.Ticks);
		break;
	case GAME_EVENT_GAME_START:
		gMission.HasStarted = true;
		break;
	case GAME_EVENT_ACTOR_ADD:
		ActorAdd(e.u.ActorAdd);
		break;
	case GAME_EVENT_ACTOR_MOVE:
		ActorMove(e.u.ActorMove);
		break;
	case GAME_EVENT_ACTOR_STATE:
		{
			TActor *a = ActorGetByUID(e.u.ActorState.UID);
			if (!a->isInUse) break;
			ActorSetState(a, (ActorAnimation)e.u.ActorState.State);
		}
		break;
	case GAME_EVENT_ACTOR_DIR:
		{
			TActor *a = ActorGetByUID(e.u.ActorDir.UID);
			if (!a->isInUse) break;
			a->direction = (direction_e)e.u.ActorDir.Dir;
		}
		break;
	case GAME_EVENT_ACTOR_SLIDE:
		{
			TActor *a = ActorGetByUID(e.u.ActorSlide.UID);
			if (!a->isInUse) break;
			a->Vel = Net2Vec2i(e.u.ActorSlide.Vel);
			// Slide sound
			if (ConfigGetBool(&gConfig, "Sound.Footsteps"))
			{
				SoundPlayAt(
					&gSoundDevice,
					gSoundDevice.slideSound,
					Vec2iNew(a->tileItem.x, a->tileItem.y));
			}
		}
		break;
	case GAME_EVENT_ACTOR_IMPULSE:
		{
			TActor *a = ActorGetByUID(e.u.ActorImpulse.UID);
			if (!a->isInUse) break;
			a->Vel = Vec2iAdd(a->Vel, Net2Vec2i(e.u.ActorImpulse.Vel));
			const Vec2i pos = Net2Vec2i(e.u.ActorImpulse.Pos);
			if (!Vec2iIsZero(pos))
			{
				a->Pos = pos;
			}
		}
		break;
	case GAME_EVENT_ACTOR_SWITCH_GUN:
		ActorSwitchGun(e.u.ActorSwitchGun);
		break;
	case GAME_EVENT_ACTOR_PICKUP_ALL:
		{
			TActor *a = ActorGetByUID(e.u.ActorPickupAll.UID);
			if (!a->isInUse) break;
			a->PickupAll = e.u.ActorPickupAll.PickupAll;
		}
		break;
	case GAME_EVENT_ACTOR_REPLACE_GUN:
		ActorReplaceGun(e.u.ActorReplaceGun);
		break;
	case GAME_EVENT_ACTOR_HEAL:
		{
			TActor *a = ActorGetByUID(e.u.Heal.UID);
			if (!a->isInUse || a->dead) break;
			ActorHeal(a, e.u.Heal.Amount);
			// Sound of healing
			SoundPlayAt(
				&gSoundDevice,
				gSoundDevice.healthSound, Vec2iFull2Real(a->Pos));
			// Tell the spawner that we took a health so we can
			// spawn more (but only if we're the server)
			if (e.u.Heal.IsRandomSpawned && !gCampaign.IsClient)
			{
				PowerupSpawnerRemoveOne(healthSpawner);
			}
			if (e.u.Heal.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_HEALTH,
					e.u.Heal.PlayerUID, e.u.Heal.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_ADD_AMMO:
		{
			TActor *a = ActorGetByUID(e.u.AddAmmo.UID);
			if (!a->isInUse || a->dead) break;
			ActorAddAmmo(a, e.u.AddAmmo.AmmoId, e.u.AddAmmo.Amount);
			// Tell the spawner that we took ammo so we can
			// spawn more (but only if we're the server)
			if (e.u.AddAmmo.IsRandomSpawned && !gCampaign.IsClient)
			{
				PowerupSpawnerRemoveOne(
					CArrayGet(ammoSpawners, e.u.AddAmmo.AmmoId));
			}
			if (e.u.AddAmmo.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_AMMO,
					e.u.AddAmmo.PlayerUID, e.u.AddAmmo.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_USE_AMMO:
		{
			TActor *a = ActorGetByUID(e.u.UseAmmo.UID);
			if (!a->isInUse || a->dead) break;
			ActorAddAmmo(a, e.u.UseAmmo.AmmoId, -(int)e.u.UseAmmo.Amount);
			if (e.u.UseAmmo.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_AMMO,
					e.u.UseAmmo.PlayerUID, -(int)e.u.UseAmmo.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_DIE:
		{
			TActor *a = ActorGetByUID(e.u.ActorDie.UID);

			// Check if the player has lives to revive
			PlayerData *p = PlayerDataGetByUID(a->PlayerUID);
			if (p != NULL)
			{
				p->Lives--;
				CASSERT(p->Lives >= 0, "Player has died too many times");
				if (p->Lives > 0 && !gCampaign.IsClient)
				{
					// Find the closest player alive; try to spawn next to that position
					// if no other suitable position exists
					Vec2i defaultSpawnPosition = Vec2iZero();
					const TActor *closestActor = AIGetClosestPlayer(a->Pos);
					if (closestActor != NULL) defaultSpawnPosition = closestActor->Pos;
					PlacePlayer(&gMap, p, defaultSpawnPosition, false);
				}
			}

			ActorDestroy(a);
		}
		break;
	case GAME_EVENT_ACTOR_MELEE:
		{
			const TActor *a = ActorGetByUID(e.u.Melee.UID);
			if (!a->isInUse) break;
			const BulletClass *b = StrBulletClass(e.u.Melee.BulletClass);
			if ((HitType)e.u.Melee.HitType != HIT_NONE &&
				HasHitSound(b->Power, a->flags, a->PlayerUID,
				(TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID,
				SPECIAL_NONE, false))
			{
				PlayHitSound(
					&b->HitSound, (HitType)e.u.Melee.HitType,
					Vec2iFull2Real(a->Pos));
			}
			if (!gCampaign.IsClient)
			{
				Damage(
					Vec2iZero(),
					b->Power,
					a->flags, a->PlayerUID, a->uid,
					(TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID,
					SPECIAL_NONE);
			}
		}
		break;
	case GAME_EVENT_ADD_PICKUP:
		PickupAdd(e.u.AddPickup);
		// Play a spawn sound
		SoundPlayAt(
			&gSoundDevice,
			StrSound("spawn_item"), Net2Vec2i(e.u.AddPickup.Pos));
		break;
	case GAME_EVENT_REMOVE_PICKUP:
		PickupDestroy(e.u.RemovePickup.UID);
		if (e.u.RemovePickup.SpawnerUID >= 0)
		{
			TObject *o = ObjGetByUID(e.u.RemovePickup.SpawnerUID);
			o->counter = AMMO_SPAWNER_RESPAWN_TICKS;
		}
		break;
	case GAME_EVENT_BULLET_BOUNCE:
		{
			TMobileObject *o = MobObjGetByUID(e.u.BulletBounce.UID);
			if (o == NULL || !o->isInUse) break;
			const Vec2i pos = Net2Vec2i(e.u.BulletBounce.BouncePos);
			PlayHitSound(
				&o->bulletClass->HitSound, (HitType)e.u.BulletBounce.HitType,
				Vec2iFull2Real(pos));
			if (e.u.BulletBounce.Spark && o->bulletClass->Spark != NULL)
			{
				GameEvent s = GameEventNew(GAME_EVENT_ADD_PARTICLE);
				s.u.AddParticle.Class = o->bulletClass->Spark;
				s.u.AddParticle.FullPos = pos;
				s.u.AddParticle.Z = o->z;
				GameEventsEnqueue(&gGameEvents, s);
			}
			o->x = pos.x;
			o->y = pos.y;
			o->vel = Net2Vec2i(e.u.BulletBounce.BounceVel);
		}
		break;
	case GAME_EVENT_REMOVE_BULLET:
		{
			TMobileObject *o = MobObjGetByUID(e.u.RemoveBullet.UID);
			if (o == NULL || !o->isInUse) break;
			MobObjDestroy(o);
		}
		break;
	case GAME_EVENT_PARTICLE_REMOVE:
		ParticleDestroy(&gParticles, e.u.ParticleRemoveId);
		break;
	case GAME_EVENT_GUN_FIRE:
		{
			const GunDescription *g = StrGunDescription(e.u.GunFire.Gun);
			const Vec2i fullPos = Net2Vec2i(e.u.GunFire.MuzzleFullPos);

			// Add bullets
			if (g->Bullet && !gCampaign.IsClient)
			{
				// Find the starting angle of the spread (clockwise)
				// Keep in mind the fencepost problem, i.e. spread of 3 means a
				// total spread angle of 2x width
				const double spreadStartAngle =
					g->AngleOffset -
					(g->Spread.Count - 1) * g->Spread.Width / 2;
				for (int i = 0; i < g->Spread.Count; i++)
				{
					const double recoil =
						((double)rand() / RAND_MAX * g->Recoil) -
						g->Recoil / 2;
					const double finalAngle =
						e.u.GunFire.Angle + spreadStartAngle +
						i * g->Spread.Width + recoil;
					GameEvent ab = GameEventNew(GAME_EVENT_ADD_BULLET);
					ab.u.AddBullet.UID = MobObjsObjsGetNextUID();
					strcpy(ab.u.AddBullet.BulletClass, g->Bullet->Name);
					ab.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos);
					ab.u.AddBullet.MuzzleHeight = e.u.GunFire.Z;
					ab.u.AddBullet.Angle = (float)finalAngle;
					ab.u.AddBullet.Elevation =
						RAND_INT(g->ElevationLow, g->ElevationHigh);
					ab.u.AddBullet.Flags = e.u.GunFire.Flags;
					ab.u.AddBullet.PlayerUID = e.u.GunFire.PlayerUID;
					ab.u.AddBullet.ActorUID = e.u.GunFire.UID;
					GameEventsEnqueue(&gGameEvents, ab);
				}
			}

			// Add muzzle flash
			if (GunHasMuzzle(g))
			{
				GameEvent ap = GameEventNew(GAME_EVENT_ADD_PARTICLE);
				ap.u.AddParticle.Class = g->MuzzleFlash;
				ap.u.AddParticle.FullPos = fullPos;
				ap.u.AddParticle.Z = e.u.GunFire.Z;
				ap.u.AddParticle.Angle = e.u.GunFire.Angle;
				GameEventsEnqueue(&gGameEvents, ap);
			}
			// Sound
			if (e.u.GunFire.Sound && g->Sound)
			{
				SoundPlayAt(&gSoundDevice, g->Sound, Vec2iFull2Real(fullPos));
			}
			// Screen shake
			if (g->ShakeAmount > 0)
			{
				GameEvent s = GameEventNew(GAME_EVENT_SCREEN_SHAKE);
				s.u.ShakeAmount = g->ShakeAmount;
				GameEventsEnqueue(&gGameEvents, s);
			}
			// Brass shells
			// If we have a reload lead, defer the creation of shells until then
			if (g->Brass && g->ReloadLead == 0)
			{
				const direction_e d = RadiansToDirection(e.u.GunFire.Angle);
				const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d);
				GunAddBrass(g, d, Vec2iMinus(fullPos, muzzleOffset));
			}
		}
		break;
	case GAME_EVENT_GUN_RELOAD:
		{
			const GunDescription *g = StrGunDescription(e.u.GunReload.Gun);
			const Vec2i fullPos = Net2Vec2i(e.u.GunReload.FullPos);
			SoundPlayAtPlusDistance(
				&gSoundDevice,
				g->ReloadSound,
				Vec2iFull2Real(fullPos),
				RELOAD_DISTANCE_PLUS);
			// Brass shells
			if (g->Brass)
			{
				GunAddBrass(g, (direction_e)e.u.GunReload.Direction, fullPos);
			}
		}
		break;
	case GAME_EVENT_GUN_STATE:
		{
			const TActor *a = ActorGetByUID(e.u.GunState.ActorUID);
			if (!a->isInUse) break;
			WeaponSetState(ActorGetGun(a), (gunstate_e)e.u.GunState.State);
		}
		break;
	case GAME_EVENT_ADD_BULLET:
		BulletAdd(e.u.AddBullet);
		break;
	case GAME_EVENT_ADD_PARTICLE:
		ParticleAdd(&gParticles, e.u.AddParticle);
		break;
	case GAME_EVENT_ACTOR_HIT:
		{
			TActor *a = ActorGetByUID(e.u.ActorHit.UID);
			if (!a->isInUse) break;
			ActorTakeHit(a, e.u.ActorHit.Special);
			if (e.u.ActorHit.Power > 0)
			{
				DamageActor(
					a, e.u.ActorHit.Power, e.u.ActorHit.HitterPlayerUID);
				if (e.u.ActorHit.PlayerUID >= 0)
				{
					HUDAddUpdate(
						&camera->HUD, NUMBER_UPDATE_HEALTH,
						e.u.ActorHit.PlayerUID, -e.u.ActorHit.Power);
				}

				AddBloodSplatter(
					a->Pos, e.u.ActorHit.Power,
					Net2Vec2i(e.u.ActorHit.Vel));
			}
		}
		break;
	case GAME_EVENT_TRIGGER:
		{
			const Tile *t =
				MapGetTile(&gMap, Net2Vec2i(e.u.TriggerEvent.Tile));
			CA_FOREACH(Trigger *, tp, t->triggers)
				if ((*tp)->id == (int)e.u.TriggerEvent.ID)
				{
					TriggerActivate(*tp, &gMap.triggers);
					break;
				}
			CA_FOREACH_END()
		}
		break;
	case GAME_EVENT_EXPLORE_TILES:
		// Process runs of explored tiles
		for (int i = 0; i < (int)e.u.ExploreTiles.Runs_count; i++)
		{
			Vec2i tile = Net2Vec2i(e.u.ExploreTiles.Runs[i].Tile);
			for (int j = 0; j < e.u.ExploreTiles.Runs[i].Run; j++)
			{
				MapMarkAsVisited(&gMap, tile);
				tile.x++;
				if (tile.x == gMap.Size.x)
				{
					tile.x = 0;
					tile.y++;
				}
			}
		}
		break;
	case GAME_EVENT_RESCUE_CHARACTER:
		{
			TActor *a = ActorGetByUID(e.u.Rescue.UID);
			if (!a->isInUse) break;
			a->flags &= ~FLAGS_PRISONER;
			SoundPlayAt(
				&gSoundDevice, StrSound("rescue"), Vec2iFull2Real(a->Pos));
		}
		break;
	case GAME_EVENT_OBJECTIVE_UPDATE:
		{
			ObjectiveDef *o = CArrayGet(
				&gMission.Objectives, e.u.ObjectiveUpdate.ObjectiveId);
			o->done += e.u.ObjectiveUpdate.Count;
			// Display a text update effect for the objective
			HUDAddUpdate(
				&camera->HUD, NUMBER_UPDATE_OBJECTIVE,
				e.u.ObjectiveUpdate.ObjectiveId, e.u.ObjectiveUpdate.Count);
			MissionSetMessageIfComplete(&gMission);
		}
		break;
	case GAME_EVENT_ADD_KEYS:
		gMission.KeyFlags |= e.u.AddKeys.KeyFlags;
		SoundPlayAt(
			&gSoundDevice, gSoundDevice.keySound, Net2Vec2i(e.u.AddKeys.Pos));
		// Clear cache since we may now have new paths
		PathCacheClear(&gPathCache);
		break;
	case GAME_EVENT_MISSION_COMPLETE:
		if (e.u.MissionComplete.ShowMsg)
		{
			HUDDisplayMessage(&camera->HUD, "Mission complete", -1);
		}
		camera->HUD.showExit = true;
		MapShowExitArea(&gMap);
		break;
	case GAME_EVENT_MISSION_INCOMPLETE:
		gMission.state = MISSION_STATE_PLAY;
		break;
	case GAME_EVENT_MISSION_PICKUP:
		gMission.state = MISSION_STATE_PICKUP;
		gMission.pickupTime = gMission.time;
		SoundPlay(&gSoundDevice, StrSound("whistle"));
		break;
	case GAME_EVENT_MISSION_END:
		gMission.isDone = true;
		break;
	default:
		assert(0 && "unknown game event");
		break;
	}
}