static int ComparePlayerScores(const void *v1, const void *v2) { const PlayerData *p1 = PlayerDataGetByUID(*(const int *)v1); const PlayerData *p2 = PlayerDataGetByUID(*(const int *)v2); int p1s = GetModeScore(p1); int p2s = GetModeScore(p2); if (p1s > p2s) { return -1; } else if (p1s < p2s) { return 1; } return 0; }
static void DisplayEquippedWeapons( const menu_t *menu, GraphicsDevice *g, const Vec2i pos, const Vec2i size, const void *data) { UNUSED(g); const WeaponMenuData *d = data; Vec2i weaponsPos; Vec2i maxTextSize = FontStrSize("LongestWeaponName"); UNUSED(menu); Vec2i dPos = pos; dPos.x -= size.x; // move to left half of screen weaponsPos = Vec2iNew( dPos.x + size.x * 3 / 4 - maxTextSize.x / 2, CENTER_Y(dPos, size, 0) + 14); const PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); if (p->weaponCount == 0) { FontStr("None selected...", weaponsPos); } else { for (int i = 0; i < p->weaponCount; i++) { FontStr( p->weapons[i]->name, Vec2iAdd(weaponsPos, Vec2iNew(0, i * FontH()))); } } }
static Character *ActorGetCharacterMutable(TActor *a) { if (a->PlayerUID >= 0) { return &PlayerDataGetByUID(a->PlayerUID)->Char; } return CArrayGet(&gCampaign.Setting.characters.OtherChars, a->charId); }
static void ShuffleOne(AppearanceMenuData *data) { PlayerData *p = PlayerDataGetByUID(data->PlayerUID); Character *c = &p->Char; int32_t *prop = (int32_t *)((char *)&c->looks + data->propertyOffset); *prop = rand() % data->menuCount; CharacterSetColors(c); }
static void DrawHealthUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 2; const TActor *a = ActorGetByUID(p->ActorUID); DrawNumUpdate(u, "%d", a->health, Vec2iNew(5, y), flags); }
static void PostInputAppearanceMenu(menu_t *menu, int cmd, void *data) { AppearanceMenuData *d = data; UNUSED(cmd); PlayerData *p = PlayerDataGetByUID(d->PlayerUID); Character *c = &p->Char; int *prop = (int *)((char *)&c->looks + d->propertyOffset); *prop = menu->u.normal.index; CharacterSetColors(c); }
static int FindLocalPlayerIndex(const int playerUID) { const PlayerData *p = PlayerDataGetByUID(playerUID); if (p == NULL || !p->IsLocal) { // This update was for a non-local player; abort return -1; } // Note: player UIDs divided by MAX_LOCAL_PLAYERS per client return playerUID % MAX_LOCAL_PLAYERS; }
static void DrawScoreUpdate(const HUDNumUpdate *u, const int flags) { if (!IsScoreNeeded(gCampaign.Entry.Mode)) { return; } const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight; DrawNumUpdate(u, "Score: %d", p->score, Vec2iNew(5, y), flags); }
static void DrawAmmoUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 4 + LIVES_ROW_EXTRA_Y; const TActor *a = ActorGetByUID(p->ActorUID); const Weapon *w = ActorGetGun(a); char gunNameBuf[256]; sprintf(gunNameBuf, "%s %%d", w->Gun->name); const int ammo = ActorGunGetAmmo(a, w); DrawNumUpdate(u, gunNameBuf, ammo, Vec2iNew(5 + GUN_ICON_PAD, y), flags); }
static void PostInputLoadTemplate(menu_t *menu, int cmd, void *data) { if (cmd & CMD_BUTTON1) { PlayerSelectMenuData *d = data; PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); const PlayerTemplate *t = CArrayGet(&gPlayerTemplates, menu->u.normal.index); memset(p->name, 0, sizeof p->name); strncpy(p->name, t->name, sizeof p->name - 1); p->Char.looks = t->Looks; CharacterSetColors(&p->Char); } }
static void ShuffleAppearance(void *data) { PlayerSelectMenuData *pData = data; char buf[512]; NameGenMake(pData->nameGenerator, buf); PlayerData *p = PlayerDataGetByUID(pData->display.PlayerUID); strncpy(p->name, buf, 20); ShuffleOne(&pData->faceData); ShuffleOne(&pData->skinData); ShuffleOne(&pData->hairData); ShuffleOne(&pData->armsData); ShuffleOne(&pData->bodyData); ShuffleOne(&pData->legsData); }
static void SaveTemplateDisplayTitle( const menu_t *menu, GraphicsDevice *g, const Vec2i pos, const Vec2i size, const void *data) { UNUSED(g); const PlayerSelectMenuData *d = data; char buf[256]; UNUSED(menu); UNUSED(size); // Display "Save <template>..." title const PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); sprintf(buf, "Save %s...", p->name); FontStr(buf, Vec2iAdd(pos, Vec2iNew(0, 0))); }
static void PostInputSaveTemplate(menu_t *menu, int cmd, void *data) { if (!(cmd & CMD_BUTTON1)) { return; } PlayerSelectMenuData *d = data; PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); while (menu->u.normal.index >= (int)gPlayerTemplates.size) { PlayerTemplate empty; memset(&empty, 0, sizeof empty); CArrayPushBack(&gPlayerTemplates, &empty); } PlayerTemplate *t = CArrayGet(&gPlayerTemplates, menu->u.normal.index); memset(t->name, 0, sizeof t->name); strncpy(t->name, p->name, sizeof t->name - 1); t->Looks = p->Char.looks; }
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; }
static void PlayerListCustomDraw( const menu_t *menu, GraphicsDevice *g, const struct vec2i pos, const struct vec2i size, const void *data) { UNUSED(menu); UNUSED(g); // Draw players starting from the index // TODO: custom columns const PlayerList *pl = data; // First draw the headers const int xStart = pos.x + 80 + (size.x - 320) / 2; int x = xStart; int y = pos.y; FontStrMask("Player", svec2i(x, y), colorPurple); x += 100; FontStrMask("Score", svec2i(x, y), colorPurple); x += 32; FontStrMask("Kills", svec2i(x, y), colorPurple); y += FontH() * 2 + PLAYER_LIST_ROW_HEIGHT + 4; // Then draw the player list int maxScore = -1; for (int i = pl->scroll; i < MIN((int)pl->playerUIDs.size, pl->scroll + PlayerListMaxRows(pl)); i++) { const int *playerUID = CArrayGet(&pl->playerUIDs, i); PlayerData *p = PlayerDataGetByUID(*playerUID); if (p == NULL) { continue; } if (maxScore < GetModeScore(p)) { maxScore = GetModeScore(p); } x = xStart; // Highlight local players using different coloured text const color_t textColor = p->IsLocal ? colorPurple : colorWhite; // Draw the players offset on alternate rows DisplayCharacterAndName( svec2i(x + (i & 1) * 16, y + 4), &p->Char, DIRECTION_DOWN, p->name, textColor); // Draw score x += 100; char buf[256]; sprintf(buf, "%d", p->Totals.Score); FontStrMask(buf, svec2i(x, y), textColor); // Draw kills x += 32; sprintf(buf, "%d", p->Totals.Kills); FontStrMask(buf, svec2i(x, y), textColor); // Draw winner/award text x += 32; if (pl->showWinners && GetModeScore(p) == maxScore) { FontStrMask("Winner!", svec2i(x, y), colorGreen); } else if (pl->showLastMan && p->Lives > 0 && gCampaign.Entry.Mode == GAME_MODE_DEATHMATCH) { // Only show last man standing on deathmatch mode FontStrMask("Last man standing!", svec2i(x, y), colorGreen); } y += PLAYER_LIST_ROW_HEIGHT; } // Draw indicator arrows if there's enough to scroll if (pl->scroll > 0) { FontStr("^", svec2i( CENTER_X(pos, size, FontStrW("^")), pos.y + FontH())); } if (pl->scroll < PlayerListMaxScroll(pl)) { FontStr("v", svec2i( CENTER_X(pos, size, FontStrW("v")), pos.y + size.y - FontH())); } // Finally draw any custom stuff if (pl->drawFunc) { pl->drawFunc(pl->data); } }
static void WeaponSelect(menu_t *menu, int cmd, void *data) { WeaponMenuData *d = data; PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); const CArray *weapons = &gMission.Weapons; // Don't process if we're not selecting a weapon if ((cmd & CMD_BUTTON1) && menu->u.normal.index < (int)weapons->size) { // Add the selected weapon // Check that the weapon hasn't been chosen yet const GunDescription **selectedWeapon = CArrayGet(weapons, menu->u.normal.index); for (int i = 0; i < p->weaponCount; i++) { if (p->weapons[i] == *selectedWeapon) { return; } } // Check that there are empty slots to add weapons if (p->weaponCount == MAX_WEAPONS) { return; } p->weapons[p->weaponCount] = *selectedWeapon; p->weaponCount++; SoundPlay(&gSoundDevice, (*selectedWeapon)->SwitchSound); // Note: need to enable before disabling otherwise // menu index is not updated properly // Enable "Done" menu item MenuEnableSubmenu(menu, (int)menu->u.normal.subMenus.size - 1); // Disable this menu entry MenuDisableSubmenu(menu, menu->u.normal.index); } else if (cmd & CMD_BUTTON2) { // Remove a weapon if (p->weaponCount > 0) { p->weaponCount--; MenuPlaySound(MENU_SOUND_BACK); // Re-enable the menu entry for this weapon const GunDescription *removedWeapon = p->weapons[p->weaponCount]; for (int i = 0; i < (int)weapons->size; i++) { const GunDescription **g = CArrayGet(weapons, i); if (*g == removedWeapon) { MenuEnableSubmenu(menu, i); break; } } } // Disable "Done" if no weapons selected if (p->weaponCount == 0) { MenuDisableSubmenu(menu, (int)menu->u.normal.subMenus.size - 1); } } }
static int HandleInputNameMenu(int cmd, void *data) { PlayerSelectMenuData *d = data; PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); if (cmd & CMD_BUTTON1) { if (d->nameMenuSelection == (int)strlen(letters)) { MenuPlaySound(MENU_SOUND_ENTER); return 1; } if (strlen(p->name) < sizeof p->name - 1) { size_t l = strlen(p->name); p->name[l + 1] = 0; if (l > 0 && p->name[l - 1] != ' ') { p->name[l] = smallLetters[d->nameMenuSelection]; } else { p->name[l] = letters[d->nameMenuSelection]; } MenuPlaySound(MENU_SOUND_ENTER); } else { MenuPlaySound(MENU_SOUND_ERROR); } } else if (cmd & CMD_BUTTON2) { if (p->name[0]) { p->name[strlen(p->name) - 1] = 0; MenuPlaySound(MENU_SOUND_BACK); } else { MenuPlaySound(MENU_SOUND_ERROR); } } else if (cmd & CMD_LEFT) { if (d->nameMenuSelection > 0) { d->nameMenuSelection--; MenuPlaySound(MENU_SOUND_SWITCH); } } else if (cmd & CMD_RIGHT) { if (d->nameMenuSelection < (int)strlen(letters)) { d->nameMenuSelection++; MenuPlaySound(MENU_SOUND_SWITCH); } } else if (cmd & CMD_UP) { if (d->nameMenuSelection >= ENTRY_COLS) { d->nameMenuSelection -= ENTRY_COLS; MenuPlaySound(MENU_SOUND_SWITCH); } } else if (cmd & CMD_DOWN) { if (d->nameMenuSelection <= (int)strlen(letters) - ENTRY_COLS) { d->nameMenuSelection += ENTRY_COLS; MenuPlaySound(MENU_SOUND_SWITCH); } else if (d->nameMenuSelection < (int)strlen(letters)) { d->nameMenuSelection = (int)strlen(letters); MenuPlaySound(MENU_SOUND_SWITCH); } } return 0; }
void PlayerSelectMenusCreate( PlayerSelectMenu *menu, int numPlayers, int player, const int playerUID, EventHandlers *handlers, GraphicsDevice *graphics, const NameGen *ng) { MenuSystem *ms = &menu->ms; PlayerSelectMenuData *data = &menu->data; Vec2i pos, size; int w = graphics->cachedConfig.Res.x; int h = graphics->cachedConfig.Res.y; data->nameMenuSelection = (int)strlen(letters); data->display.PlayerUID = playerUID; data->display.currentMenu = &ms->current; data->PlayerUID = playerUID; data->nameGenerator = ng; switch (numPlayers) { case 1: // Single menu, entire screen pos = Vec2iNew(w / 2, 0); size = Vec2iNew(w / 2, h); break; case 2: // Two menus, side by side pos = Vec2iNew(player * w / 2 + w / 4, 0); size = Vec2iNew(w / 4, h); break; case 3: case 4: // Four corners pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2); size = Vec2iNew(w / 4, h / 2); break; default: CASSERT(false, "not implemented"); pos = Vec2iNew(w / 2, 0); size = Vec2iNew(w / 2, h); break; } MenuSystemInit(ms, handlers, graphics, pos, size); ms->align = MENU_ALIGN_LEFT; ms->root = ms->current = MenuCreateNormal( "", "", MENU_TYPE_NORMAL, 0); MenuAddSubmenu( ms->root, MenuCreateCustom( "Name", DrawNameMenu, HandleInputNameMenu, data)); MenuAddSubmenu( ms->root, CreateCustomizeMenu("Customize...", data, playerUID)); MenuAddSubmenu( ms->root, MenuCreateVoidFunc("Shuffle", ShuffleAppearance, data)); MenuAddSubmenu(ms->root, CreateUseTemplateMenu("Load", data)); MenuAddSubmenu(ms->root, CreateSaveTemplateMenu("Save", data)); MenuAddSubmenu(ms->root, MenuCreateSeparator("")); MenuAddSubmenu( ms->root, MenuCreateNormal("Done", "", MENU_TYPE_NORMAL, 0)); // Select "Done" ms->root->u.normal.index = (int)ms->root->u.normal.subMenus.size - 1; MenuAddExitType(ms, MENU_TYPE_RETURN); MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, data); MenuSystemAddCustomDisplay( ms, MenuDisplayPlayerControls, &data->PlayerUID); // Detect when there have been new player templates created, // to re-enable the load menu CheckReenableLoadMenu(ms->root, NULL); MenuSetPostEnterFunc(ms->root, CheckReenableLoadMenu, NULL, false); PlayerData *p = PlayerDataGetByUID(playerUID); CharacterSetColors(&p->Char); }
void WeaponMenuCreate( WeaponMenu *menu, int numPlayers, int player, const int playerUID, EventHandlers *handlers, GraphicsDevice *graphics) { MenuSystem *ms = &menu->ms; WeaponMenuData *data = &menu->data; Vec2i pos, size; int w = graphics->cachedConfig.Res.x; int h = graphics->cachedConfig.Res.y; data->display.PlayerUID = playerUID; data->display.currentMenu = &ms->current; data->display.Dir = DIRECTION_DOWN; data->PlayerUID = playerUID; switch (numPlayers) { case 1: // Single menu, entire screen pos = Vec2iNew(w / 2, 0); size = Vec2iNew(w / 2, h); break; case 2: // Two menus, side by side pos = Vec2iNew(player * w / 2 + w / 4, 0); size = Vec2iNew(w / 4, h); break; case 3: case 4: // Four corners pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2); size = Vec2iNew(w / 4, h / 2); break; default: CASSERT(false, "not implemented"); pos = Vec2iNew(w / 2, 0); size = Vec2iNew(w / 2, h); break; } MenuSystemInit(ms, handlers, graphics, pos, size); ms->align = MENU_ALIGN_LEFT; ms->root = ms->current = MenuCreateNormal( "", "", MENU_TYPE_NORMAL, 0); ms->root->u.normal.maxItems = 11; const CArray *weapons = &gMission.Weapons; for (int i = 0; i < (int)weapons->size; i++) { const GunDescription **g = CArrayGet(weapons, i); menu_t *gunMenu; if ((*g)->Description != NULL) { // Gun description menu gunMenu = MenuCreateNormal((*g)->name, "", MENU_TYPE_NORMAL, 0); char *buf; CMALLOC(buf, strlen((*g)->Description) * 2); FontSplitLines((*g)->Description, buf, size.x * 5 / 6); MenuAddSubmenu(gunMenu, MenuCreateBack(buf)); CFREE(buf); gunMenu->u.normal.isSubmenusAlt = true; MenuSetCustomDisplay(gunMenu, DisplayDescriptionGunIcon, *g); } else { gunMenu = MenuCreate((*g)->name, MENU_TYPE_BASIC); } MenuAddSubmenu(ms->root, gunMenu); } MenuSetPostInputFunc(ms->root, WeaponSelect, &data->display); // Disable menu items where the player already has the weapon PlayerData *pData = PlayerDataGetByUID(playerUID); for (int i = 0; i < pData->weaponCount; i++) { for (int j = 0; j < (int)weapons->size; j++) { const GunDescription **g = CArrayGet(weapons, j); if (pData->weapons[i] == *g) { MenuDisableSubmenu(ms->root, j); } } } MenuAddSubmenu(ms->root, MenuCreateSeparator("")); MenuAddSubmenu( ms->root, MenuCreateNormal("(End)", "", MENU_TYPE_NORMAL, 0)); // Select "(End)" ms->root->u.normal.index = (int)ms->root->u.normal.subMenus.size - 1; // Disable "Done" if no weapons selected if (pData->weaponCount == 0) { MenuDisableSubmenu(ms->root, (int)ms->root->u.normal.subMenus.size - 1); } MenuSetCustomDisplay(ms->root, DisplayGunIcon, NULL); MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, &data->display); MenuSystemAddCustomDisplay(ms, DisplayEquippedWeapons, data); MenuSystemAddCustomDisplay( ms, MenuDisplayPlayerControls, &data->PlayerUID); }
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; } }