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, ¶ms); // 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); }
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, ¶ms); // 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); }
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, ¶ms); // 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); }
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, ¶ms); // 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); }
// 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], ¶ms); // 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); }
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, ¶ms); // 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); }
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); } } }
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; } }