static void DestroyObject( TObject *o, const int flags, const int playerUID, const int uid) { o->Health = 0; const Vec2i realPos = Vec2iNew(o->tileItem.x, o->tileItem.y); if (!gCampaign.IsClient) { // Update objective UpdateMissionObjective(&gMission, o->tileItem.flags, OBJECTIVE_DESTROY); // Extra score if objective if ((o->tileItem.flags & TILEITEM_OBJECTIVE) && playerUID >= 0) { GameEvent e = GameEventNew(GAME_EVENT_SCORE); e.u.Score.PlayerUID = playerUID; e.u.Score.Score = OBJECT_SCORE; GameEventsEnqueue(&gGameEvents, e); } // Weapons that go off when this object is destroyed const Vec2i fullPos = Vec2iReal2Full(realPos); for (int i = 0; i < (int)o->Class->DestroyGuns.size; i++) { const GunDescription **g = CArrayGet(&o->Class->DestroyGuns, i); GunFire(*g, fullPos, 0, 0, flags, playerUID, uid, true, false); } // A wreck left after the destruction of this object // TODO: doesn't need to be network event GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET); e.u.AddBullet.UID = MobObjsObjsGetNextUID(); strcpy(e.u.AddBullet.BulletClass, "fireball_wreck"); e.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos); e.u.AddBullet.MuzzleHeight = 0; e.u.AddBullet.Angle = 0; e.u.AddBullet.Elevation = 0; e.u.AddBullet.Flags = 0; e.u.AddBullet.PlayerUID = -1; e.u.AddBullet.ActorUID = -1; GameEventsEnqueue(&gGameEvents, e); } SoundPlayAt(&gSoundDevice, gSoundDevice.wreckSound, realPos); // Turn the object into a wreck, if available if (o->Class->Wreck.Pic) { o->tileItem.flags = TILEITEM_IS_WRECK; } else { ObjDestroy(o->tileItem.id); } // Update pathfinding cache since this object could have blocked a path // before PathCacheClear(&gPathCache); }
static void DoDamageCharacter( const Vec2i hitVector, const int power, const int flags, const int playerUID, const int uid, TActor *actor, const special_damage_e special) { // Create events: hit, damage, score CASSERT(actor->isInUse, "Cannot damage nonexistent player"); CASSERT(CanHitCharacter(flags, uid, actor), "damaging undamageable actor"); if (ConfigGetBool(&gConfig, "Game.ShotsPushback")) { GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE); ei.u.ActorImpulse.UID = actor->uid; ei.u.ActorImpulse.Vel = Vec2i2Net(Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR)); ei.u.ActorImpulse.Pos = Vec2i2Net(actor->Pos); GameEventsEnqueue(&gGameEvents, ei); } const bool canDamage = CanDamageCharacter(flags, playerUID, uid, actor, special); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_HIT); e.u.ActorHit.UID = actor->uid; e.u.ActorHit.PlayerUID = actor->PlayerUID; e.u.ActorHit.HitterPlayerUID = playerUID; e.u.ActorHit.Special = special; e.u.ActorHit.Power = canDamage ? power : 0; e.u.ActorHit.Vel = Vec2i2Net(hitVector); GameEventsEnqueue(&gGameEvents, e); if (canDamage) { // Don't score for friendly or player hits const bool isFriendly = (actor->flags & FLAGS_GOOD_GUY) || (!IsPVP(gCampaign.Entry.Mode) && actor->PlayerUID >= 0); if (playerUID >= 0 && power != 0 && !isFriendly) { // Calculate score based on // if they hit a penalty character e = GameEventNew(GAME_EVENT_SCORE); e.u.Score.PlayerUID = playerUID; if (actor->flags & FLAGS_PENALTY) { e.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e); } } }
void GunAddBullets( const GunDescription *g, const Vec2i fullPos, const int z, const double radians, const int flags, const int player, const int uid, const bool playSound) { // Add bullets if (g->Bullet) { // 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 = radians + spreadStartAngle + i * g->Spread.Width + recoil; GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET); strcpy(e.u.AddBullet.BulletClass, g->Bullet->Name); e.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos); e.u.AddBullet.MuzzleHeight = z; e.u.AddBullet.Angle = (float)finalAngle; e.u.AddBullet.Elevation = RAND_INT(g->ElevationLow, g->ElevationHigh); e.u.AddBullet.Flags = flags; e.u.AddBullet.PlayerIndex = player; e.u.AddBullet.UID = uid; GameEventsEnqueue(&gGameEvents, e); } } // Add muzzle flash if (GunHasMuzzle(g)) { GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE); e.u.AddParticle.Class = g->MuzzleFlash; e.u.AddParticle.FullPos = fullPos; e.u.AddParticle.Z = z; e.u.AddParticle.Angle = radians; GameEventsEnqueue(&gGameEvents, e); } // Sound if (playSound && g->Sound) { SoundPlayAt(&gSoundDevice, g->Sound, Vec2iFull2Real(fullPos)); } // Screen shake if (g->ShakeAmount > 0) { GameEvent shake = GameEventNew(GAME_EVENT_SCREEN_SHAKE); shake.u.ShakeAmount = g->ShakeAmount; GameEventsEnqueue(&gGameEvents, shake); } }
void Damage( const Vec2i hitVector, const int power, const int flags, const int playerUID, const int uid, const TileItemKind targetKind, const int targetUID, const special_damage_e special) { switch (targetKind) { case KIND_CHARACTER: DoDamageCharacter( hitVector, power, flags, playerUID, uid, ActorGetByUID(targetUID), special); break; case KIND_OBJECT: { GameEvent e = GameEventNew(GAME_EVENT_MAP_OBJECT_DAMAGE); e.u.MapObjectDamage.UID = targetUID; e.u.MapObjectDamage.Power = power; e.u.MapObjectDamage.ActorUID = uid; e.u.MapObjectDamage.PlayerUID = playerUID; e.u.MapObjectDamage.Flags = flags; GameEventsEnqueue(&gGameEvents, e); } break; default: CASSERT(false, "cannot damage tile item kind"); break; } }
void CreateEnemies(void) { if (gCampaign.Setting.characters.baddieIds.size == 0) { return; } for (int i = 0; i < MAX(1, (gMission.missionData->EnemyDensity * ConfigGetInt(&gConfig, "Game.EnemyDensity")) / 100); i++) { NActorAdd aa = NActorAdd_init_default; aa.UID = ActorsGetNextUID(); aa.CharId = CharacterStoreGetRandomBaddieId( &gCampaign.Setting.characters); aa.FullPos = PlaceAwayFromPlayers(&gMap); aa.Direction = rand() % DIRECTION_COUNT; const Character *c = CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId); aa.Health = CharacterGetStartingHealth(c, true); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD); e.u.ActorAdd = aa; GameEventsEnqueue(&gGameEvents, e); gBaddieCount++; // Process the events that actually place the actors HandleGameEvents(&gGameEvents, NULL, NULL, NULL); } }
void MissionSetMessageIfComplete(struct MissionOptions *options) { if (CanCompleteMission(options)) { GameEvent msg; msg.Type = GAME_EVENT_MISSION_COMPLETE; GameEventsEnqueue(&gGameEvents, msg); } }
bool RunGame(const CampaignOptions *co, struct MissionOptions *m, Map *map) { // Clear the background DrawRectangle( &gGraphicsDevice, Vec2iZero(), gGraphicsDevice.cachedConfig.Res, colorBlack, 0); SDL_UpdateTexture( gGraphicsDevice.bkg, NULL, gGraphicsDevice.buf, gGraphicsDevice.cachedConfig.Res.x * sizeof(Uint32)); MapLoad(map, m, co); // Seed random if PVP mode (otherwise players will always spawn in same // position) if (IsPVP(co->Entry.Mode)) { srand((unsigned int)time(NULL)); } if (!co->IsClient) { MapLoadDynamic(map, m, &co->Setting.characters); // For PVP modes, mark all map as explored if (IsPVP(co->Entry.Mode)) { MapMarkAllAsVisited(map); } // Reset players for the mission CA_FOREACH(const PlayerData, p, gPlayerDatas) // Only reset for local players; for remote ones wait for the // client ready message if (!p->IsLocal) continue; GameEvent e = GameEventNew(GAME_EVENT_PLAYER_DATA); e.u.PlayerData = PlayerDataMissionReset(p); GameEventsEnqueue(&gGameEvents, e); CA_FOREACH_END() // Process the events to force add the players HandleGameEvents(&gGameEvents, NULL, NULL, NULL); // Note: place players first, // as bad guys are placed away from players Vec2i firstPos = Vec2iZero(); CA_FOREACH(const PlayerData, p, gPlayerDatas) if (!p->Ready) continue; firstPos = PlacePlayer(&gMap, p, firstPos, true); CA_FOREACH_END() if (!IsPVP(co->Entry.Mode)) { InitializeBadGuys(); CreateEnemies(); } }
bool NumPlayersSelection(GraphicsDevice *graphics, EventHandlers *handlers) { MenuSystem ms; MenuSystemInit( &ms, handlers, graphics, Vec2iZero(), graphics->cachedConfig.Res); ms.allowAborts = true; ms.root = ms.current = MenuCreateNormal( "", "Select number of players", MENU_TYPE_NORMAL, 0); MenuAddSubmenu(ms.current, MenuCreateReturn("(No local players)", 0)); for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { char buf[2]; sprintf(buf, "%d", i + 1); MenuAddSubmenu(ms.current, MenuCreateReturn(buf, i + 1)); } MenuAddExitType(&ms, MENU_TYPE_RETURN); // Select 1 player default ms.current->u.normal.index = 1; MenuLoop(&ms); const bool ok = !ms.hasAbort; if (ok) { const int numPlayers = ms.current->u.returnCode; CA_FOREACH(const PlayerData, p, gPlayerDatas) CASSERT(!p->IsLocal, "unexpected local player"); CA_FOREACH_END() // Add the players for (int i = 0; i < numPlayers; i++) { GameEvent e = GameEventNew(GAME_EVENT_PLAYER_DATA); e.u.PlayerData = PlayerDataDefault(i); e.u.PlayerData.UID = gNetClient.FirstPlayerUID + i; GameEventsEnqueue(&gGameEvents, e); } // Process the events to force add the players HandleGameEvents(&gGameEvents, NULL, NULL, NULL); // This also causes the client to send player data to the server } MenuSystemTerminate(&ms); return ok; }
void EmitterStart( Emitter *em, const Vec2i fullPos, const int z, const Vec2i vel) { const Vec2i p = Vec2iAdd(fullPos, Vec2iReal2Full(em->offset)); // TODO: single event multiple particles GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE); e.u.AddParticle.FullPos = p; e.u.AddParticle.Z = z * Z_FACTOR; e.u.AddParticle.Class = em->p; const int speed = RAND_INT(em->minSpeed, em->maxSpeed); const Vec2i baseVel = Vec2iFromPolar(speed, RAND_DOUBLE(0, PI * 2)); e.u.AddParticle.Vel = Vec2iAdd(vel, baseVel); e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); e.u.AddParticle.DZ = RAND_INT(em->minDZ, em->maxDZ); e.u.AddParticle.Spin = RAND_DOUBLE(em->minRotation, em->maxRotation); GameEventsEnqueue(&gGameEvents, e); }
bool DamageSomething( const Vec2i hitVector, const int power, const int flags, const int player, const int uid, TTileItem *target, const special_damage_e special, const HitSounds *hitSounds, const bool allowFriendlyHitSound) { if (!target) { return 0; } const Vec2i pos = Vec2iNew(target->x, target->y); switch (target->kind) { case KIND_CHARACTER: return DoDamageCharacter( pos, hitVector, power, flags, player, uid, target, special, hitSounds, allowFriendlyHitSound); case KIND_OBJECT: DamageObject(power, flags, player, uid, target); if (gConfig.Sound.Hits && hitSounds != NULL && power > 0) { GameEvent es; es.Type = GAME_EVENT_SOUND_AT; es.u.SoundAt.Sound = hitSounds->Object; es.u.SoundAt.Pos = pos; GameEventsEnqueue(&gGameEvents, es); } break; case KIND_PARTICLE: case KIND_MOBILEOBJECT: break; } return 1; }
Vec2i PlacePlayer( Map *map, const PlayerData *p, const Vec2i firstPos, const bool pumpEvents) { NetMsgActorAdd aa = NetMsgActorAdd_init_default; aa.Id = ActorsGetFreeIndex(); aa.Health = p->Char.maxHealth; aa.PlayerId = p->playerIndex; if (IsPVP(gCampaign.Entry.Mode)) { // In a PVP mode, always place players apart aa.FullPos = PlaceActor(&gMap); } else if (gMission.missionData->Type == MAPTYPE_STATIC && !Vec2iIsZero(gMission.missionData->u.Static.Start)) { // place players near the start point Vec2i startPoint = Vec2iReal2Full(Vec2iCenterOfTile( gMission.missionData->u.Static.Start)); aa.FullPos = PlaceActorNear(map, startPoint, true); } else if (gConfig.Interface.Splitscreen == SPLITSCREEN_NEVER && !Vec2iIsZero(firstPos)) { // If never split screen, try to place players near the first player aa.FullPos = PlaceActorNear(map, firstPos, true); } else { aa.FullPos = PlaceActor(map); } GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD); e.u.ActorAdd = aa; GameEventsEnqueue(&gGameEvents, e); if (pumpEvents) { // Process the events that actually place the players HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers); } return Vec2iNew(aa.FullPos.x, aa.FullPos.y); }
void UpdateObjects(const int ticks) { for (int i = 0; i < (int)gObjs.size; i++) { TObject *obj = CArrayGet(&gObjs, i); if (!obj->isInUse) { continue; } switch (obj->Class->Type) { case MAP_OBJECT_TYPE_PICKUP_SPAWNER: if (gCampaign.IsClient) break; // If counter -1, it is inactive i.e. already spawned pickup if (obj->counter == -1) { break; } obj->counter -= ticks; if (obj->counter <= 0) { // Deactivate spawner by setting counter to -1 // Spawner reactivated only when ammo taken obj->counter = -1; GameEvent e = GameEventNew(GAME_EVENT_ADD_PICKUP); e.u.AddPickup.UID = PickupsGetNextUID(); strcpy( e.u.AddPickup.PickupClass, obj->Class->u.PickupClass->Name); e.u.AddPickup.IsRandomSpawned = false; e.u.AddPickup.SpawnerUID = obj->uid; e.u.AddPickup.TileItemFlags = 0; e.u.AddPickup.Pos = Vec2i2Net(Vec2iNew(obj->tileItem.x, obj->tileItem.y)); GameEventsEnqueue(&gGameEvents, e); } break; default: // Do nothing break; } } }
static void AddPickupAtObject(const TObject *o, const PickupType type) { GameEvent e = GameEventNew(GAME_EVENT_ADD_PICKUP); switch (type) { case PICKUP_JEWEL: CASSERT(false, "unexpected pickup type"); break; case PICKUP_HEALTH: if (!ConfigGetBool(&gConfig, "Game.HealthPickups")) { return; } strcpy(e.u.AddPickup.PickupClass, "health"); break; case PICKUP_AMMO: if (!ConfigGetBool(&gConfig, "Game.Ammo")) { return; } // Pick a random ammo type and spawn it { const int ammoId = rand() % AmmoGetNumClasses(&gAmmo); const Ammo *a = AmmoGetById(&gAmmo, ammoId); sprintf(e.u.AddPickup.PickupClass, "ammo_%s", a->Name); } break; case PICKUP_KEYCARD: CASSERT(false, "unexpected pickup type"); break; case PICKUP_GUN: // Pick a random mission gun type and spawn it { const int gunId = (int)(rand() % gMission.Weapons.size); const WeaponClass **wc = CArrayGet(&gMission.Weapons, gunId); sprintf(e.u.AddPickup.PickupClass, "gun_%s", (*wc)->name); } break; default: CASSERT(false, "unexpected pickup type"); break; } e.u.AddPickup.UID = PickupsGetNextUID(); e.u.AddPickup.Pos = Vec2ToNet(o->thing.Pos); e.u.AddPickup.IsRandomSpawned = true; e.u.AddPickup.SpawnerUID = -1; e.u.AddPickup.ThingFlags = 0; GameEventsEnqueue(&gGameEvents, e); }
void UpdateMobileObjects(int ticks) { for (int i = 0; i < (int)gMobObjs.size; i++) { TMobileObject *obj = CArrayGet(&gMobObjs, i); if (!obj->isInUse) { continue; } if (!obj->updateFunc(obj, ticks) && !gCampaign.IsClient) { GameEvent e = GameEventNew(GAME_EVENT_REMOVE_BULLET); e.u.RemoveBullet.UID = obj->UID; GameEventsEnqueue(&gGameEvents, e); continue; } CPicUpdate(&obj->tileItem.CPic, ticks); } }
void DamageObject(const NThingDamage d) { TObject *o = ObjGetByUID(d.UID); // Don't bother if object already destroyed if (o->Health <= 0) { return; } o->Health -= d.Power; // Destroying objects and all the wonderful things that happen if (o->Health <= 0 && !gCampaign.IsClient) { GameEvent e = GameEventNew(GAME_EVENT_MAP_OBJECT_REMOVE); e.u.MapObjectRemove.UID = o->uid; e.u.MapObjectRemove.ActorUID = d.SourceActorUID; e.u.MapObjectRemove.Flags = d.Flags; GameEventsEnqueue(&gGameEvents, e); } }
void ObjRemove(const NMapObjectRemove mor) { TObject *o = ObjGetByUID(mor.UID); o->Health = 0; if (!gCampaign.IsClient) { // Update objective UpdateMissionObjective( &gMission, o->thing.flags, OBJECTIVE_DESTROY, 1); const TActor *a = ActorGetByUID(mor.ActorUID); const int playerUID = a != NULL ? a->PlayerUID : -1; // Extra score if objective if ((o->thing.flags & THING_OBJECTIVE) && playerUID >= 0) { GameEvent e = GameEventNew(GAME_EVENT_SCORE); e.u.Score.PlayerUID = playerUID; e.u.Score.Score = OBJECT_SCORE; GameEventsEnqueue(&gGameEvents, e); } // Weapons that go off when this object is destroyed CA_FOREACH(const WeaponClass *, wc, o->Class->DestroyGuns) WeaponClassFire( *wc, o->thing.Pos, 0, 0, mor.Flags, mor.ActorUID, true, false); CA_FOREACH_END() // Random chance to add pickups in single player modes if (!IsPVP(gCampaign.Entry.Mode)) { CA_FOREACH( const MapObjectDestroySpawn, mods, o->Class->DestroySpawn) const double chance = (double) rand() / RAND_MAX; if (chance < mods->SpawnChance) { AddPickupAtObject(o, mods->Type); } CA_FOREACH_END() }
void UpdateMissionObjective( struct MissionOptions *options, int flags, ObjectiveType type, int player, Vec2i pos) { if (!(flags & TILEITEM_OBJECTIVE)) { return; } int idx = ObjectiveFromTileItem(flags); MissionObjective *mobj = CArrayGet(&options->missionData->Objectives, idx); if (mobj->Type != type) { return; } GameEvent e; e.Type = GAME_EVENT_UPDATE_OBJECTIVE; e.u.UpdateObjective.ObjectiveIndex = idx; e.u.UpdateObjective.Update = 1; e.u.UpdateObjective.PlayerIndex = player; e.u.UpdateObjective.Pos = pos; GameEventsEnqueue(&gGameEvents, e); }
void UpdateMobileObjects(int ticks) { for (int i = 0; i < (int)gMobObjs.size; i++) { TMobileObject *obj = CArrayGet(&gMobObjs, i); if (!obj->isInUse) { continue; } if ((*(obj->updateFunc))(obj, ticks) == 0) { GameEvent e; e.Type = GAME_EVENT_MOBILE_OBJECT_REMOVE; e.u.MobileObjectRemoveId = i; GameEventsEnqueue(&gGameEvents, e); } else { CPicUpdate(&obj->tileItem.CPic, ticks); } } }
static void PlayerSpecialCommands(TActor *actor, const int cmd) { if ((cmd & CMD_BUTTON2) && CMD_HAS_DIRECTION(cmd)) { if (ConfigGetEnum(&gConfig, "Game.SwitchMoveStyle") == SWITCHMOVE_SLIDE) { SlideActor(actor, cmd); } } else if ( (actor->lastCmd & CMD_BUTTON2) && !(cmd & CMD_BUTTON2) && !actor->specialCmdDir && !actor->CanPickupSpecial && !(ConfigGetEnum(&gConfig, "Game.SwitchMoveStyle") == SWITCHMOVE_SLIDE && CMD_HAS_DIRECTION(cmd)) && ActorCanSwitchGun(actor)) { GameEvent e = GameEventNew(GAME_EVENT_ACTOR_SWITCH_GUN); e.u.ActorSwitchGun.UID = actor->uid; e.u.ActorSwitchGun.GunIdx = (actor->gunIndex + 1) % actor->guns.size; GameEventsEnqueue(&gGameEvents, e); } }
static void AddBrass( const GunDescription *g, const direction_e d, const Vec2i pos) { CASSERT(g->Brass, "Cannot create brass for no-brass weapon"); GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE); e.u.AddParticle.Class = g->Brass; double x, y; const double radians = dir2radians[d]; GetVectorsForRadians(radians, &x, &y); const Vec2i ejectionPortOffset = Vec2iReal2Full(Vec2iScale(Vec2iNew( (int)round(x), (int)round(y)), 7)); const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d); const Vec2i muzzlePosition = Vec2iAdd(pos, muzzleOffset); e.u.AddParticle.FullPos = Vec2iMinus(muzzlePosition, ejectionPortOffset); e.u.AddParticle.Z = g->MuzzleHeight; e.u.AddParticle.Vel = Vec2iScaleDiv( GetFullVectorsForRadians(radians + PI / 2), 3); e.u.AddParticle.Vel.x += (rand() % 128) - 64; e.u.AddParticle.Vel.y += (rand() % 128) - 64; e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); e.u.AddParticle.DZ = (rand() % 6) + 6; e.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, e); }
static void OnReceive(NetClient *n, ENetEvent event) { const NetMsg msgType = (NetMsg)*(uint32_t *)event.packet->data; LOG(LM_NET, LL_TRACE, "recv msg(%u)", msgType); const NetMsgEntry nme = NetMsgGet(msgType); if (nme.Event != GAME_EVENT_NONE) { // Game event message; decode and add to event queue LOG(LM_NET, LL_DEBUG, "recv gameEvent(%d)", (int)nme.Event); GameEvent e = GameEventNew(nme.Event); if (nme.Fields != NULL) { NetDecode(event.packet, &e.u, nme.Fields); } // For actor events, check if UID is not for local player int actorUID = -1; bool actorIsLocal = false; switch (nme.Event) { case GAME_EVENT_ACTOR_ADD: // Note: ignore checking this event break; case GAME_EVENT_ACTOR_MOVE: actorUID = e.u.ActorMove.UID; break; case GAME_EVENT_ACTOR_STATE: actorUID = e.u.ActorState.UID; break; case GAME_EVENT_ACTOR_DIR: actorUID = e.u.ActorDir.UID; break; case GAME_EVENT_ADD_BULLET: actorIsLocal = PlayerIsLocal(e.u.AddBullet.PlayerIndex); break; default: break; } if (actorUID >= 0) { actorIsLocal = ActorIsLocalPlayer(actorUID); } if (actorIsLocal) { LOG(LM_NET, LL_TRACE, "game event is for local player, ignoring"); } else { GameEventsEnqueue(&gGameEvents, e); } } else { switch (msgType) { case MSG_CLIENT_ID: { CASSERT( n->ClientId == -1, "unexpected client ID message, already set"); NetMsgClientId cid; NetDecode(event.packet, &cid, NetMsgClientId_fields); LOG(LM_NET, LL_DEBUG, "NetClient: received client ID %d", cid.Id); n->ClientId = cid.Id; } break; case MSG_CAMPAIGN_DEF: if (gCampaign.IsLoaded) { LOG(LM_NET, LL_INFO, "WARNING: unexpected campaign def msg received"); } else { LOG(LM_NET, LL_DEBUG, "NetClient: received campaign def, loading..."); NetMsgCampaignDef def; NetDecode(event.packet, &def, NetMsgCampaignDef_fields); char campaignPath[CDOGS_PATH_MAX]; GameMode mode; NetMsgCampaignDefConvert(&def, campaignPath, &mode); CampaignEntry entry; if (CampaignEntryTryLoad(&entry, campaignPath, mode) && CampaignLoad(&gCampaign, &entry)) { gCampaign.IsClient = true; } else { printf("Error: failed to load campaign def\n"); gCampaign.IsError = true; } } break; case MSG_PLAYER_DATA: { NetMsgPlayerData pd; NetDecode(event.packet, &pd, NetMsgPlayerData_fields); AddMissingPlayers(pd.PlayerIndex); LOG(LM_NET, LL_DEBUG, "recv player data name(%s) id(%d) total(%d)", pd.Name, pd.PlayerIndex, (int)gPlayerDatas.size); NetMsgPlayerDataUpdate(&pd); } break; case MSG_ADD_PLAYERS: { NetMsgAddPlayers ap; NetDecode(event.packet, &ap, NetMsgAddPlayers_fields); LOG(LM_NET, LL_DEBUG, "NetClient: received new players %d", (int)ap.PlayerIds_count); // Add new players // If they are local players, set them up with defaults const bool isLocal = ap.ClientId == n->ClientId; for (int i = 0; i < ap.PlayerIds_count; i++) { const int playerId = (int)ap.PlayerIds[i]; AddMissingPlayers(playerId); PlayerData *p = CArrayGet(&gPlayerDatas, playerId); p->IsLocal = isLocal; if (isLocal) { PlayerDataSetLocalDefaults(p, i); } } } break; case MSG_GAME_START: LOG(LM_NET, LL_DEBUG, "NetClient: received game start"); gMission.HasStarted = true; break; case MSG_GAME_END: LOG(LM_NET, LL_DEBUG, "NetClient: received game end"); gMission.isDone = true; break; default: CASSERT(false, "unexpected message type"); break; } } enet_packet_destroy(event.packet); }
static void DamageObject( const int power, const int flags, const int player, const int uid, TTileItem *target) { TObject *object = CArrayGet(&gObjs, target->id); // Don't bother if object already destroyed if (object->structure <= 0) { return; } object->structure -= power; const Vec2i pos = Vec2iNew(target->x, target->y); // Destroying objects and all the wonderful things that happen if (object->structure <= 0) { object->structure = 0; UpdateMissionObjective( &gMission, object->tileItem.flags, OBJECTIVE_DESTROY, player, pos); if (object->flags & OBJFLAG_QUAKE) { GameEvent shake; shake.Type = GAME_EVENT_SCREEN_SHAKE; shake.u.ShakeAmount = SHAKE_BIG_AMOUNT; GameEventsEnqueue(&gGameEvents, shake); } const Vec2i fullPos = Vec2iReal2Full( Vec2iNew(object->tileItem.x, object->tileItem.y)); if (object->flags & OBJFLAG_EXPLOSIVE) { GunAddBullets( StrGunDescription("explosion1"), fullPos, 0, 0, flags, player, uid, true); GunAddBullets( StrGunDescription("explosion2"), fullPos, 0, 0, flags, player, uid, true); GunAddBullets( StrGunDescription("explosion3"), fullPos, 0, 0, flags, player, uid, true); } else if (object->flags & OBJFLAG_FLAMMABLE) { GunAddBullets( StrGunDescription("fire_explosion"), fullPos, 0, 0, flags, player, uid, true); } else if (object->flags & OBJFLAG_POISONOUS) { GunAddBullets( StrGunDescription("gas_poison_explosion"), fullPos, 0, 0, flags, player, uid, true); } else if (object->flags & OBJFLAG_CONFUSING) { GunAddBullets( StrGunDescription("gas_confuse_explosion"), fullPos, 0, 0, flags, player, uid, true); } else { // A wreck left after the destruction of this object GameEvent e; memset(&e, 0, sizeof e); e.Type = GAME_EVENT_ADD_BULLET; e.u.AddBullet.BulletClass = StrBulletClass("fireball_wreck"); e.u.AddBullet.MuzzlePos = fullPos; e.u.AddBullet.MuzzleHeight = 0; e.u.AddBullet.Angle = 0; e.u.AddBullet.Elevation = 0; e.u.AddBullet.Flags = 0; e.u.AddBullet.PlayerIndex = -1; e.u.AddBullet.UID = -1; GameEventsEnqueue(&gGameEvents, e); SoundPlayAt( &gSoundDevice, gSoundDevice.wreckSound, Vec2iNew(object->tileItem.x, object->tileItem.y)); } if (object->wreckedPic) { object->tileItem.flags = TILEITEM_IS_WRECK; object->pic = object->wreckedPic; object->picName = ""; } else { ObjDestroy(object->tileItem.id); } } }
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; } }
void InitializeBadGuys(void) { for (int i = 0; i < (int)gMission.missionData->Objectives.size; i++) { MissionObjective *mobj = CArrayGet(&gMission.missionData->Objectives, i); ObjectiveDef *obj = CArrayGet(&gMission.Objectives, i); if (mobj->Type == OBJECTIVE_KILL && gMission.missionData->SpecialChars.size > 0) { for (; obj->placed < mobj->Count; obj->placed++) { NActorAdd aa = NActorAdd_init_default; aa.UID = ActorsGetNextUID(); aa.CharId = CharacterStoreGetRandomSpecialId( &gCampaign.Setting.characters); aa.TileItemFlags = ObjectiveToTileItem(i); aa.Direction = rand() % DIRECTION_COUNT; const Character *c = CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId); aa.Health = CharacterGetStartingHealth(c, true); aa.FullPos = PlaceAwayFromPlayers(&gMap); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD); e.u.ActorAdd = aa; GameEventsEnqueue(&gGameEvents, e); // Process the events that actually place the actors HandleGameEvents(&gGameEvents, NULL, NULL, NULL); } } else if (mobj->Type == OBJECTIVE_RESCUE) { for (; obj->placed < mobj->Count; obj->placed++) { NActorAdd aa = NActorAdd_init_default; aa.UID = ActorsGetNextUID(); aa.CharId = CharacterStoreGetPrisonerId( &gCampaign.Setting.characters, 0); aa.TileItemFlags = ObjectiveToTileItem(i); aa.Direction = rand() % DIRECTION_COUNT; const Character *c = CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId); aa.Health = CharacterGetStartingHealth(c, true); if (MapHasLockedRooms(&gMap)) { aa.FullPos = PlacePrisoner(&gMap); } else { aa.FullPos = PlaceAwayFromPlayers(&gMap); } GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD); e.u.ActorAdd = aa; GameEventsEnqueue(&gGameEvents, e); // Process the events that actually place the actors HandleGameEvents(&gGameEvents, NULL, NULL, NULL); } } } gBaddieCount = gMission.index * 4; gAreGoodGuysPresent = 0; }
void CommandBadGuys(int ticks) { int count = 0; int delayModifier; int rollLimit; switch (ConfigGetEnum(&gConfig, "Game.Difficulty")) { case DIFFICULTY_VERYEASY: delayModifier = 4; rollLimit = 300; break; case DIFFICULTY_EASY: delayModifier = 2; rollLimit = 200; break; case DIFFICULTY_HARD: delayModifier = 1; rollLimit = 75; break; case DIFFICULTY_VERYHARD: delayModifier = 1; rollLimit = 50; break; default: delayModifier = 1; rollLimit = 100; break; } CA_FOREACH(TActor, actor, gActors) if (!actor->isInUse) { continue; } const CharBot *bot = ActorGetCharacter(actor)->bot; if (!(actor->PlayerUID >= 0 || (actor->flags & FLAGS_PRISONER))) { if ((actor->flags & (FLAGS_VICTIM | FLAGS_GOOD_GUY)) != 0) { gAreGoodGuysPresent = 1; } count++; int cmd = 0; // Wake up if it can see a player if ((actor->flags & FLAGS_SLEEPING) && actor->aiContext->Delay == 0) { if (CanSeeAPlayer(actor)) { actor->flags &= ~FLAGS_SLEEPING; ActorSetAIState(actor, AI_STATE_NONE); } actor->aiContext->Delay = bot->actionDelay * delayModifier; // Randomly change direction int newDir = (int)actor->direction + ((rand() % 2) * 2 - 1); if (newDir < (int)DIRECTION_UP) { newDir = (int)DIRECTION_UPLEFT; } if (newDir == (int)DIRECTION_COUNT) { newDir = (int)DIRECTION_UP; } cmd = DirectionToCmd((int)newDir); } // Go to sleep if the player's too far away if (!(actor->flags & FLAGS_SLEEPING) && actor->aiContext->Delay == 0 && !(actor->flags & FLAGS_AWAKEALWAYS)) { if (!IsCloseToPlayer(actor->Pos, (40 * 16) << 8)) { actor->flags |= FLAGS_SLEEPING; ActorSetAIState(actor, AI_STATE_IDLE); } } if (!actor->dead && !(actor->flags & FLAGS_SLEEPING)) { bool bypass = false; const int roll = rand() % rollLimit; if (actor->flags & FLAGS_FOLLOWER) { if (IsCloseToPlayer(actor->Pos, 32 << 8)) { cmd = 0; ActorSetAIState(actor, AI_STATE_IDLE); } else { cmd = AIGoto( actor, AIGetClosestPlayerPos(actor->Pos), true); ActorSetAIState(actor, AI_STATE_FOLLOW); } } else if (!!(actor->flags & FLAGS_SNEAKY) && !!(actor->flags & FLAGS_VISIBLE) && DidPlayerShoot()) { cmd = AIHuntClosest(actor) | CMD_BUTTON1; if (actor->flags & FLAGS_RUNS_AWAY) { // Turn back and shoot for running away characters cmd = AIReverseDirection(cmd); } bypass = true; ActorSetAIState(actor, AI_STATE_HUNT); } else if (actor->flags & FLAGS_DETOURING) { cmd = BrightWalk(actor, roll); ActorSetAIState(actor, AI_STATE_TRACK); } else if (actor->aiContext->Delay > 0) { cmd = actor->lastCmd & ~CMD_BUTTON1; } else { if (roll < bot->probabilityToTrack) { cmd = AIHuntClosest(actor); ActorSetAIState(actor, AI_STATE_HUNT); } else if (roll < bot->probabilityToMove) { cmd = DirectionToCmd(rand() & 7); ActorSetAIState(actor, AI_STATE_TRACK); } else { cmd = 0; } actor->aiContext->Delay = bot->actionDelay * delayModifier; } if (!bypass) { if (WillFire(actor, roll)) { cmd |= CMD_BUTTON1; if (!!(actor->flags & FLAGS_FOLLOWER) && (actor->flags & FLAGS_GOOD_GUY)) { // Shoot in a random direction away for (int j = 0; j < 10; j++) { direction_e d = (direction_e)(rand() % DIRECTION_COUNT); if (!IsFacingPlayer(actor, d)) { cmd = DirectionToCmd(d) | CMD_BUTTON1; break; } } } if (actor->flags & FLAGS_RUNS_AWAY) { // Turn back and shoot for running away characters cmd |= AIReverseDirection(AIHuntClosest(actor)); } ActorSetAIState(actor, AI_STATE_HUNT); } else { if ((actor->flags & FLAGS_VISIBLE) == 0) { // I think this is some hack to make sure invisible enemies don't fire so much ActorGetGun(actor)->lock = 40; } if (cmd && !IsDirectionOK(actor, CmdToDirection(cmd)) && (actor->flags & FLAGS_DETOURING) == 0) { Detour(actor); cmd = 0; ActorSetAIState(actor, AI_STATE_TRACK); } } } } actor->aiContext->Delay = MAX(0, actor->aiContext->Delay - ticks); CommandActor(actor, cmd, ticks); } else if ((actor->flags & FLAGS_PRISONER) != 0) { CommandActor(actor, 0, ticks); } CA_FOREACH_END() if (gMission.missionData->Enemies.size > 0 && gMission.missionData->EnemyDensity > 0 && count < MAX(1, (gMission.missionData->EnemyDensity * ConfigGetInt(&gConfig, "Game.EnemyDensity")) / 100)) { NActorAdd aa = NActorAdd_init_default; aa.UID = ActorsGetNextUID(); aa.CharId = CharacterStoreGetRandomBaddieId( &gCampaign.Setting.characters); aa.Direction = rand() % DIRECTION_COUNT; const Character *c = CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId); aa.Health = CharacterGetStartingHealth(c, true); aa.FullPos = PlaceAwayFromPlayers(&gMap); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD); e.u.ActorAdd = aa; GameEventsEnqueue(&gGameEvents, e); gBaddieCount++; } }
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 bool DoDamageCharacter( const Vec2i pos, const Vec2i hitVector, const int power, const int flags, const int player, const int uid, const TTileItem *target, const special_damage_e special, const HitSounds *hitSounds, const bool allowFriendlyHitSound) { // Create events: hit, damage, score TActor *actor = CArrayGet(&gActors, target->id); CASSERT(actor->isInUse, "Cannot damage nonexistent player"); bool canHit = CanHitCharacter(flags, uid, actor); if (canHit) { GameEvent e; e.Type = GAME_EVENT_HIT_CHARACTER; e.u.HitCharacter.TargetId = actor->tileItem.id; e.u.HitCharacter.Special = special; GameEventsEnqueue(&gGameEvents, e); if (gConfig.Sound.Hits && hitSounds != NULL && !ActorIsImmune(actor, special) && (allowFriendlyHitSound || !ActorIsInvulnerable( actor, flags, player, gCampaign.Entry.Mode))) { GameEvent es; es.Type = GAME_EVENT_SOUND_AT; es.u.SoundAt.Sound = hitSounds->Flesh; es.u.SoundAt.Pos = pos; GameEventsEnqueue(&gGameEvents, es); } if (gConfig.Game.ShotsPushback) { GameEvent ei; ei.Type = GAME_EVENT_ACTOR_IMPULSE; ei.u.ActorImpulse.Id = actor->tileItem.id; ei.u.ActorImpulse.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR); GameEventsEnqueue(&gGameEvents, ei); } if (CanDamageCharacter(flags, player, uid, actor, special)) { GameEvent e1; e1.Type = GAME_EVENT_DAMAGE_CHARACTER; e1.u.DamageCharacter.Power = power; e1.u.DamageCharacter.PlayerIndex = player; e1.u.DamageCharacter.TargetId = actor->tileItem.id; e1.u.DamageCharacter.TargetPlayerIndex = -1; if (actor->pData) { e1.u.DamageCharacter.TargetPlayerIndex = actor->pData->playerIndex; } GameEventsEnqueue(&gGameEvents, e1); if (gConfig.Game.Gore != GORE_NONE) { GameEvent eb; memset(&eb, 0, sizeof eb); eb.Type = GAME_EVENT_ADD_PARTICLE; eb.u.AddParticle.FullPos = Vec2iReal2Full(pos); eb.u.AddParticle.Z = 10 * Z_FACTOR; int bloodPower = power * 2; int bloodSize = 1; while (bloodPower > 0) { switch (bloodSize) { case 1: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood1"); break; case 2: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood2"); break; default: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood3"); break; } bloodSize++; if (bloodSize > 3) { bloodSize = 1; } if (gConfig.Game.ShotsPushback) { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, (rand() % 8 + 8) * power), 15 * SHOT_IMPULSE_DIVISOR); } else { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, rand() % 8 + 8), 20); } eb.u.AddParticle.Vel.x += (rand() % 128) - 64; eb.u.AddParticle.Vel.y += (rand() % 128) - 64; eb.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); eb.u.AddParticle.DZ = (rand() % 6) + 6; eb.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, eb); switch (gConfig.Game.Gore) { case GORE_LOW: bloodPower /= 8; break; case GORE_MEDIUM: bloodPower /= 2; break; default: bloodPower = bloodPower * 7 / 8; break; } } } if (player >= 0 && power != 0) { // Calculate score based on // if they hit a penalty character GameEvent e2; e2.Type = GAME_EVENT_SCORE; e2.u.Score.PlayerIndex = player; if (actor->flags & FLAGS_PENALTY) { e2.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e2.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e2); } } } return canHit; }