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); }
void ObjAddDestructible( Vec2i pos, Vec2i size, const TOffsetPic *pic, const TOffsetPic *wreckedPic, const char *picName, int structure, int objFlags, int tileFlags) { Vec2i fullPos = Vec2iReal2Full(pos); TObject *o = CArrayGet(&gObjs, ObjAdd( fullPos, size, picName, OBJ_NONE, tileFlags)); o->pic = pic; o->wreckedPic = wreckedPic; o->structure = structure; o->flags = objFlags; }
NetMsgVec2i PlaceBaddie(Map *map) { // Don't try forever trying to place baddie for (int i = 0; i < 100; i++) { // Try spawning out of players' sights const Vec2i pos = Vec2iReal2Full(Vec2iNew( rand() % (map->Size.x * TILE_WIDTH), rand() % (map->Size.y * TILE_HEIGHT))); const TActor *closestPlayer = AIGetClosestPlayer(pos); if (closestPlayer && CHEBYSHEV_DISTANCE( pos.x, pos.y, closestPlayer->Pos.x, closestPlayer->Pos.y) >= 256 * 150 && MapIsTileAreaClear(map, pos, Vec2iNew(ACTOR_W, ACTOR_H))) { NetMsgVec2i posNet; posNet.x = pos.x; posNet.y = pos.y; return posNet; } } // Keep trying, but this time try spawning anywhere, // even close to player for (;;) { const Vec2i pos = Vec2iReal2Full(Vec2iNew( rand() % (map->Size.x * TILE_WIDTH), rand() % (map->Size.y * TILE_HEIGHT))); if (MapIsTileAreaClear(map, pos, Vec2iNew(ACTOR_W, ACTOR_H))) { NetMsgVec2i posNet; posNet.x = pos.x; posNet.y = pos.y; return posNet; } } }
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); }
Vec2i GunGetMuzzleOffset(const GunDescription *desc, const direction_e dir) { if (!GunHasMuzzle(desc)) { return Vec2iZero(); } gunpic_e g = desc->pic; CASSERT(g >= 0, "Gun has no pic"); int body = (int)g < 0 ? BODY_UNARMED : BODY_ARMED; Vec2i position = Vec2iNew( cGunHandOffset[body][dir].dx + cGunPics[g][dir][GUNSTATE_FIRING].dx + cMuzzleOffset[g][dir].dx, cGunHandOffset[body][dir].dy + cGunPics[g][dir][GUNSTATE_FIRING].dy + cMuzzleOffset[g][dir].dy + BULLET_Z); return Vec2iReal2Full(position); }
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); }
static bool TryPlacePickup(HealthPickups *h) { Vec2i size = Vec2iNew(HEALTH_W, HEALTH_H); // Attempt to place one in unexplored area for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); if (!Vec2iIsZero(v) && !MapGetTile(h->map, Vec2iToTile(v))->isVisited) { MapPlaceHealth(v); return true; } } // Attempt to place one in out-of-sight area for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); const Vec2i fullpos = Vec2iReal2Full(v); const TActor *closestPlayer = AIGetClosestPlayer(fullpos); if (!Vec2iIsZero(v) && (!closestPlayer || CHEBYSHEV_DISTANCE( fullpos.x, fullpos.y, closestPlayer->Pos.x, closestPlayer->Pos.y) >= 256 * 150)) { MapPlaceHealth(v); return true; } } // Attempt to place one anyway for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); if (!Vec2iIsZero(v)) { MapPlaceHealth(v); return true; } } return false; }
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 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; }
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); } } }