void PlayHitSound(const HitSounds *h, const HitType t, const Vec2i realPos) { switch (t) { case HIT_NONE: // Do nothing break; case HIT_WALL: SoundPlayAt(&gSoundDevice, StrSound(h->Wall), realPos); break; case HIT_OBJECT: SoundPlayAt(&gSoundDevice, StrSound(h->Object), realPos); break; case HIT_FLESH: SoundPlayAt(&gSoundDevice, StrSound(h->Flesh), realPos); break; default: CASSERT(false, "unknown hit type") break; } }
bool ScreenMissionSummary(CampaignOptions *c, struct MissionOptions *m) { // Save password MissionSave ms; MissionSaveInit(&ms); ms.Campaign = c->Entry; // Don't make password for next level if there is none int passwordIndex = m->index + 1; if (passwordIndex == c->Entry.NumMissions) { passwordIndex--; } strcpy(ms.Password, MakePassword(passwordIndex, 0)); ms.MissionsCompleted = m->index + 1; AutosaveAddMission(&gAutosave, &ms, ms.Campaign.BuiltinIndex); AutosaveSave(&gAutosave, GetConfigFilePath(AUTOSAVE_FILE)); // Calculate bonus scores // Bonuses only apply if at least one player has lived if (AreAnySurvived()) { int bonus = 0; // Objective bonuses for (int i = 0; i < (int)m->missionData->Objectives.size; i++) { const ObjectiveDef *o = CArrayGet(&m->Objectives, i); const MissionObjective *mo = CArrayGet(&m->missionData->Objectives, i); if (o->done == mo->Count && o->done > mo->Required) { // Perfect bonus += PERFECT_BONUS; } } bonus += GetAccessBonus(m); bonus += GetTimeBonus(m, NULL); for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); ApplyBonuses(p, bonus); } } GameLoopWaitForAnyKeyOrButtonData wData; GameLoopData gData = GameLoopDataNew( &wData, GameLoopWaitForAnyKeyOrButtonFunc, m, MissionSummaryDraw); GameLoop(&gData); if (wData.IsOK) { SoundPlay(&gSoundDevice, StrSound("mg")); } return wData.IsOK; }
void LoadSoundFromNode(Mix_Chunk **value, json_t *node, const char *name) { if (json_find_first_label(node, name) == NULL) { return; } if (!TryLoadValue(&node, name)) { return; } *value = StrSound(node->text); }
bool ScreenCampaignIntro(CampaignSetting *c) { GameLoopWaitForAnyKeyOrButtonData wData; GameLoopData gData = GameLoopDataNew( &wData, GameLoopWaitForAnyKeyOrButtonFunc, c, CampaignIntroDraw); GameLoop(&gData); if (wData.IsOK) { SoundPlay(&gSoundDevice, StrSound("mg")); } return wData.IsOK; }
void MenuPlaySound(MenuSound s) { switch (s) { case MENU_SOUND_ENTER: SoundPlay(&gSoundDevice, StrSound("mg")); break; case MENU_SOUND_BACK: SoundPlay(&gSoundDevice, StrSound("pickup")); break; case MENU_SOUND_SWITCH: SoundPlay(&gSoundDevice, StrSound("door")); break; case MENU_SOUND_START: SoundPlay(&gSoundDevice, StrSound("hahaha")); break; case MENU_SOUND_ERROR: SoundPlay(&gSoundDevice, SoundGetRandomScream(&gSoundDevice)); break; default: break; } }
static int PlayerListInput(int cmd, void *data) { // Input: up/down scrolls list // CMD 1/2: exit PlayerList *pl = data; // Note: players can leave due to network disconnection // Update our lists CA_FOREACH(const int, playerUID, pl->playerUIDs) const PlayerData *p = PlayerDataGetByUID(*playerUID); if (p == NULL) { CArrayDelete(&pl->playerUIDs, _ca_index); _ca_index--; } CA_FOREACH_END() if (cmd == CMD_DOWN) { SoundPlay(&gSoundDevice, StrSound("door")); pl->scroll++; } else if (cmd == CMD_UP) { SoundPlay(&gSoundDevice, StrSound("door")); pl->scroll--; } else if (AnyButton(cmd)) { SoundPlay(&gSoundDevice, StrSound("pickup")); return 1; } // Scroll wrap-around pl->scroll = CLAMP_OPPOSITE(pl->scroll, 0, PlayerListMaxScroll(pl)); return 0; }
GameLoopData *ScreenVictory(CampaignOptions *c) { SoundPlay(&gSoundDevice, StrSound("victory")); VictoryData *data; CMALLOC(data, sizeof *data); data->Campaign = c; const char *finalWordsSingle[] = { "Ha, next time I'll use my good hand", "Over already? I was just warming up...", "There's just no good opposition to be found these days!", "Well, maybe I'll just do my monthly reload then", "Woof woof", "I'll just bury the bones in the back yard, he-he", "I just wish they'd let me try bare-handed", "Rambo? Who's Rambo?", "<in Austrian accent:> I'll be back", "Gee, my trigger finger is sore", "I need more practice. I think I missed a few shots at times" }; const char *finalWordsMulti[] = { "United we stand, divided we conquer", "Nothing like good teamwork, is there?", "Which way is the camera?", "We eat bullets for breakfast and have grenades as dessert", "We're so cool we have to wear mittens", }; if (GetNumPlayers(PLAYER_ANY, false, true) == 1) { const int numWords = sizeof finalWordsSingle / sizeof(char *); data->FinalWords = finalWordsSingle[rand() % numWords]; } else { const int numWords = sizeof finalWordsMulti / sizeof(char *); data->FinalWords = finalWordsMulti[rand() % numWords]; } PlayerList *pl = PlayerListNew( PlayerListUpdate, VictoryDraw, data, true, false); pl->pos.y = 75; pl->size.y -= pl->pos.y; return PlayerListLoop(pl); }
void SoundInitialize(SoundDevice *device, const char *path) { memset(device, 0, sizeof *device); if (OpenAudio(44100, AUDIO_S16, 2, 1024) != 0) { return; } device->channels = 64; SoundReconfigure(device); CArrayInit(&device->sounds, sizeof(SoundData)); CArrayInit(&device->customSounds, sizeof(SoundData)); SoundLoadDirImpl(device, path, NULL); // Look for commonly used sounds to set our pointers CArrayInit(&device->footstepSounds, sizeof(Mix_Chunk *)); for (int i = 0;; i++) { char buf[CDOGS_FILENAME_MAX]; sprintf(buf, "footsteps/%d", i); Mix_Chunk *s = StrSound(buf); if (s == NULL) break; CArrayPushBack(&device->footstepSounds, &s); } device->slideSound = StrSound("slide"); device->healthSound = StrSound("health"); device->clickSound = StrSound("click"); device->keySound = StrSound("key"); device->wreckSound = StrSound("bang"); CArrayInit(&device->screamSounds, sizeof(Mix_Chunk *)); for (int i = 0;; i++) { char buf[CDOGS_FILENAME_MAX]; sprintf(buf, "aargh%d", i); Mix_Chunk *scream = StrSound(buf); if (scream == NULL) { break; } CArrayPushBack(&device->screamSounds, &scream); } }
static void HandleGameEvent( GameEvent *e, HUD *hud, ScreenShake *shake, HealthPickups *hp, EventHandlers *eventHandlers) { switch (e->Type) { case GAME_EVENT_SCORE: Score(&gPlayerDatas[e->u.Score.PlayerIndex], e->u.Score.Score); HUDAddScoreUpdate(hud, e->u.Score.PlayerIndex, e->u.Score.Score); break; case GAME_EVENT_SOUND_AT: if (e->u.SoundAt.Sound) { SoundPlayAt( &gSoundDevice, e->u.SoundAt.Sound, e->u.SoundAt.Pos); } break; case GAME_EVENT_SCREEN_SHAKE: *shake = ScreenShakeAdd( *shake, e->u.ShakeAmount, gConfig.Graphics.ShakeMultiplier); break; case GAME_EVENT_SET_MESSAGE: HUDDisplayMessage( hud, e->u.SetMessage.Message, e->u.SetMessage.Ticks); break; case GAME_EVENT_GAME_START: if (eventHandlers->netInput.channel.state == CHANNEL_STATE_CONNECTED) { NetInputSendMsg( &eventHandlers->netInput, SERVER_MSG_GAME_START); } break; case GAME_EVENT_ADD_HEALTH_PICKUP: MapPlaceHealth(e->u.AddPos); break; case GAME_EVENT_TAKE_HEALTH_PICKUP: if (IsPlayerAlive(e->u.PickupPlayer)) { TActor *player = CArrayGet( &gActors, gPlayerIds[e->u.PickupPlayer]); ActorHeal(player, HEALTH_PICKUP_HEAL_AMOUNT); HealthPickupsRemoveOne(hp); HUDAddHealthUpdate( hud, e->u.PickupPlayer, HEALTH_PICKUP_HEAL_AMOUNT); } break; case GAME_EVENT_MOBILE_OBJECT_REMOVE: MobObjDestroy(e->u.MobileObjectRemoveId); break; case GAME_EVENT_ADD_BULLET: BulletAdd( e->u.AddBullet.Bullet, e->u.AddBullet.MuzzlePos, e->u.AddBullet.MuzzleHeight, e->u.AddBullet.Angle, e->u.AddBullet.Direction, e->u.AddBullet.Flags, e->u.AddBullet.PlayerIndex); break; case GAME_EVENT_ADD_MUZZLE_FLASH: AddMuzzleFlash( e->u.AddMuzzleFlash.FullPos, e->u.AddMuzzleFlash.MuzzleHeight, e->u.AddMuzzleFlash.Sprites, e->u.AddMuzzleFlash.Direction, e->u.AddMuzzleFlash.Color, e->u.AddMuzzleFlash.Duration); break; case GAME_EVENT_ADD_FIREBALL: AddFireball(e->u.AddFireball); break; case GAME_EVENT_HIT_CHARACTER: HitCharacter( e->u.HitCharacter.Flags, e->u.HitCharacter.PlayerIndex, CArrayGet(&gActors, e->u.HitCharacter.TargetId), e->u.HitCharacter.Special, e->u.HitCharacter.HasHitSound); break; case GAME_EVENT_ACTOR_IMPULSE: { TActor *a = CArrayGet(&gActors, e->u.ActorImpulse.Id); a->Vel = Vec2iAdd(a->Vel, e->u.ActorImpulse.Vel); } break; case GAME_EVENT_DAMAGE_CHARACTER: DamageCharacter( e->u.DamageCharacter.Power, e->u.DamageCharacter.PlayerIndex, CArrayGet(&gActors, e->u.DamageCharacter.TargetId)); if (e->u.DamageCharacter.Power != 0) { HUDAddHealthUpdate( hud, e->u.DamageCharacter.TargetPlayerIndex, -e->u.DamageCharacter.Power); } break; case GAME_EVENT_TRIGGER: { const Tile *t = MapGetTile(&gMap, e->u.Trigger.TilePos); for (int i = 0; i < (int)t->triggers.size; i++) { Trigger **tp = CArrayGet(&t->triggers, i); if ((*tp)->id == e->u.Trigger.Id) { TriggerActivate(*tp, &gMap.triggers); break; } } } break; case GAME_EVENT_UPDATE_OBJECTIVE: { struct Objective *o = CArrayGet( &gMission.Objectives, e->u.UpdateObjective.ObjectiveIndex); o->done += e->u.UpdateObjective.Update; MissionObjective *mo = CArrayGet( &gMission.missionData->Objectives, e->u.UpdateObjective.ObjectiveIndex); switch (mo->Type) { case OBJECTIVE_COLLECT: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = PICKUP_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; case OBJECTIVE_DESTROY: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = OBJECT_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; default: // No other special objective handling break; } // Display a text update effect for the objective HUDAddObjectiveUpdate( hud, e->u.UpdateObjective.ObjectiveIndex, e->u.UpdateObjective.Update); MissionSetMessageIfComplete(&gMission); } break; case GAME_EVENT_MISSION_COMPLETE: HUDDisplayMessage(hud, "Mission complete", -1); 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; } }
static void AssignPlayerInputDevices(EventHandlers *handlers) { bool assignedKeyboards[MAX_KEYBOARD_CONFIGS]; bool assignedMouse = false; bool assignedJoysticks[MAX_JOYSTICKS]; memset(assignedKeyboards, 0, sizeof assignedKeyboards); memset(assignedJoysticks, 0, sizeof assignedJoysticks); for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!p->IsLocal) { continue; } if (p->inputDevice != INPUT_DEVICE_UNSET) { // Find all the assigned devices switch (p->inputDevice) { case INPUT_DEVICE_KEYBOARD: assignedKeyboards[p->deviceIndex] = true; break; case INPUT_DEVICE_MOUSE: assignedMouse = true; break; case INPUT_DEVICE_JOYSTICK: assignedJoysticks[p->deviceIndex] = true; break; default: // do nothing break; } continue; } // Try to assign devices to players // For each unassigned player, check if any device has button 1 pressed for (int j = 0; j < MAX_KEYBOARD_CONFIGS; j++) { char buf[256]; sprintf(buf, "Input.PlayerKeys%d.button1", j); if (KeyIsPressed(&handlers->keyboard, ConfigGetInt(&gConfig, buf)) && !assignedKeyboards[j]) { PlayerSetInputDevice(p, INPUT_DEVICE_KEYBOARD, j); assignedKeyboards[j] = true; SoundPlay(&gSoundDevice, StrSound("hahaha")); break; } } if (MouseIsPressed(&handlers->mouse, SDL_BUTTON_LEFT) && !assignedMouse) { PlayerSetInputDevice(p, INPUT_DEVICE_MOUSE, 0); assignedMouse = true; SoundPlay(&gSoundDevice, StrSound("hahaha")); continue; } for (int j = 0; j < handlers->joysticks.numJoys; j++) { if (JoyIsPressed( &handlers->joysticks.joys[j], CMD_BUTTON1) && !assignedJoysticks[j]) { PlayerSetInputDevice(p, INPUT_DEVICE_JOYSTICK, j); assignedJoysticks[j] = true; SoundPlay(&gSoundDevice, StrSound("hahaha")); break; } } } }
static void PlayerListOnExit(GameLoopData *data) { UNUSED(data); SoundPlay(&gSoundDevice, StrSound("mg")); }
bool UpdateBullet(TMobileObject *obj, const int ticks) { obj->count += ticks; obj->soundLock = MAX(0, obj->soundLock - ticks); obj->specialLock = MAX(0, obj->specialLock - ticks); if (obj->count < obj->bulletClass->Delay) { return true; } if (obj->range >= 0 && obj->count > obj->range) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->OutOfRangeGuns); } return false; } const Vec2i objPos = Vec2iNew(obj->x, obj->y); if (obj->bulletClass->SeekFactor > 0) { // Find the closest target to this bullet and steer towards it const TActor *owner = ActorGetByUID(obj->ActorUID); if (owner == NULL) { return false; } const TActor *target = AIGetClosestEnemy(objPos, owner, obj->flags); if (target && !target->dead) { for (int i = 0; i < ticks; i++) { obj->vel = SeekTowards( objPos, obj->vel, obj->bulletClass->SpeedLow, target->Pos, obj->bulletClass->SeekFactor); } } } Vec2i pos = Vec2iScale(Vec2iAdd(objPos, obj->vel), ticks); HitType hitItem = HIT_NONE; if (!gCampaign.IsClient) { hitItem = HitItem(obj, pos, obj->bulletClass->Persists); } const Vec2i realPos = Vec2iFull2Real(pos); // Falling (grenades) if (obj->bulletClass->Falling.GravityFactor != 0) { bool hasDropped = obj->z <= 0; for (int i = 0; i < ticks; i++) { obj->z += obj->dz; if (obj->z <= 0) { obj->z = 0; if (obj->bulletClass->Falling.Bounces) { obj->dz = -obj->dz / 2; } else { obj->dz = 0; } if (!hasDropped) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->Falling.DropGuns); } } hasDropped = true; if (obj->bulletClass->Falling.DestroyOnDrop) { return false; } SoundPlayAt( &gSoundDevice, StrSound(obj->bulletClass->HitSound.Wall), realPos); } else { obj->dz -= obj->bulletClass->Falling.GravityFactor; } if (!obj->bulletClass->Falling.FallsDown) { obj->dz = MAX(0, obj->dz); } } } // Friction const bool isDiagonal = obj->vel.x != 0 && obj->vel.y != 0; int frictionComponent = isDiagonal ? (int)round(obj->bulletClass->Friction / sqrt(2)) : obj->bulletClass->Friction; for (int i = 0; i < ticks; i++) { if (obj->vel.x > 0) { obj->vel.x -= frictionComponent; } else if (obj->vel.x < 0) { obj->vel.x += frictionComponent; } if (obj->vel.y > 0) { obj->vel.y -= frictionComponent; } else if (obj->vel.y < 0) { obj->vel.y += frictionComponent; } } bool hitWall = false; if (!gCampaign.IsClient && hitItem == HIT_NONE) { hitWall = MapIsRealPosIn(&gMap, realPos) && ShootWall(realPos.x, realPos.y); } if (hitWall || hitItem != HIT_NONE) { GameEvent b = GameEventNew(GAME_EVENT_BULLET_BOUNCE); b.u.BulletBounce.UID = obj->UID; if (hitWall && !Vec2iIsZero(obj->vel)) { b.u.BulletBounce.HitType = (int)HIT_WALL; } else { b.u.BulletBounce.HitType = (int)hitItem; } bool alive = true; if ((hitWall && !obj->bulletClass->WallBounces) || ((hitItem != HIT_NONE) && obj->bulletClass->HitsObjects)) { b.u.BulletBounce.Spark = true; CASSERT(!gCampaign.IsClient, "Cannot process bounces as client"); FireGuns(obj, &obj->bulletClass->HitGuns); if (hitWall || !obj->bulletClass->Persists) { alive = false; } } b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(obj->vel); if (hitWall && !Vec2iIsZero(obj->vel)) { // Bouncing Vec2i bounceVel = obj->vel; pos = GetWallBounceFullPos(objPos, pos, &bounceVel); b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(bounceVel); obj->vel = bounceVel; } GameEventsEnqueue(&gGameEvents, b); if (!alive) { return false; } } if (!MapTryMoveTileItem(&gMap, &obj->tileItem, realPos)) { obj->count = obj->range; return false; } obj->x = pos.x; obj->y = pos.y; if (obj->bulletClass->Erratic) { for (int i = 0; i < ticks; i++) { obj->vel.x += ((rand() % 3) - 1) * 128; obj->vel.y += ((rand() % 3) - 1) * 128; } } // Proximity function, destroy // Only check proximity every now and then if (obj->bulletClass->ProximityGuns.size > 0 && !(obj->count & 3)) { if (!gCampaign.IsClient) { // Detonate the mine if there are characters in the tiles around it const Vec2i tv = Vec2iToTile(Vec2iFull2Real(pos)); Vec2i dv; for (dv.y = -1; dv.y <= 1; dv.y++) { for (dv.x = -1; dv.x <= 1; dv.x++) { const Vec2i dtv = Vec2iAdd(tv, dv); if (!MapIsTileIn(&gMap, dtv)) { continue; } if (TileHasCharacter(MapGetTile(&gMap, dtv))) { FireGuns(obj, &obj->bulletClass->ProximityGuns); return false; } } } } } return true; }
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; } }