static bool CanSeeAPlayer(const TActor *a) { const Vec2i realPos = Vec2iFull2Real(a->Pos); CA_FOREACH(const PlayerData, p, gPlayerDatas) if (!IsPlayerAlive(p)) { continue; } const TActor *player = ActorGetByUID(p->ActorUID); const Vec2i playerRealPos = Vec2iFull2Real(player->Pos); // Can see player if: // - Clear line of sight, and // - If they are close, or if facing and they are not too far if (!AIHasClearShot(realPos, playerRealPos)) { continue; } const int distance = CHEBYSHEV_DISTANCE( realPos.x, realPos.y, playerRealPos.x, playerRealPos.y); const bool isClose = distance < 16 * 4; const bool isNotTooFar = distance < 16 * 30; if (isClose || (isNotTooFar && AIIsFacing(a, player->Pos, a->direction))) { return true; } CA_FOREACH_END() return false; }
static bool CanSeeAPlayer(const TActor *a) { const Vec2i realPos = Vec2iFull2Real(a->Pos); for (int i = 0; i < MAX_PLAYERS; i++) { if (!IsPlayerAlive(i)) { continue; } const TActor *player = CArrayGet(&gActors, gPlayerIds[i]); const Vec2i playerRealPos = Vec2iFull2Real(player->Pos); // Can see player if: // - Clear line of sight, and // - If they are close, or if facing and they are not too far if (!AIHasClearShot(realPos, playerRealPos)) { continue; } const int distance = CHEBYSHEV_DISTANCE( realPos.x, realPos.y, playerRealPos.x, playerRealPos.y); const bool isClose = distance < 16 * 4; const bool isNotTooFar = distance < 16 * 30; if (isClose || (isNotTooFar && IsFacing(realPos, playerRealPos, a->direction))) { return true; } } return false; }
Vec2i GetWallBounceFullPos( const Vec2i startFull, const Vec2i newFull, Vec2i *velFull) { CASSERT(velFull != NULL, "need velocity for wall bouncing"); Vec2i newReal = Vec2iFull2Real(newFull); if (!ShootWall(newReal.x, newReal.y)) { return newFull; } Vec2i startRealPos = Vec2iFull2Real(startFull); Vec2i bounceFull = startFull; if (!ShootWall(startRealPos.x, newReal.y)) { bounceFull.y = newFull.y; velFull->x *= -1; } else if (!ShootWall(newReal.x, startRealPos.y)) { bounceFull.x = newFull.x; velFull->y *= -1; } else { *velFull = Vec2iScale(*velFull, -1); // Keep bouncing back if it's inside a wall // However, do not bounce more than half a tile's size if (!Vec2iIsZero(*velFull)) { Vec2i bounceReal = newReal; const int maxBounces = MAX( velFull->x / 256 / TILE_WIDTH, velFull->y / 256 / TILE_HEIGHT); for (int i = 0; i < maxBounces && MapIsRealPosIn(&gMap, bounceReal) && ShootWall(bounceReal.x, bounceReal.y); i++) { bounceFull = Vec2iAdd(bounceFull, *velFull); bounceReal = Vec2iFull2Real(bounceFull); } // If still colliding wall or outside map, // can't recover from this point; zero velocity and return if (!MapIsRealPosIn(&gMap, bounceReal) || ShootWall(bounceReal.x, bounceReal.y)) { *velFull = Vec2iZero(); return startFull; } } } return bounceFull; }
void DrawLaserSight( const ActorPics *pics, const TActor *a, const Vec2i picPos) { // Don't draw if dead or transparent if (pics->IsDead || pics->IsTransparent) return; // Check config const LaserSight ls = ConfigGetEnum(&gConfig, "Game.LaserSight"); if (ls != LASER_SIGHT_ALL && !(ls == LASER_SIGHT_PLAYERS && a->PlayerUID >= 0)) { return; } // Draw weapon indicators const GunDescription *g = ActorGetGun(a)->Gun; Vec2i muzzlePos = Vec2iAdd( picPos, Vec2iFull2Real(GunGetMuzzleOffset(g, a->direction))); muzzlePos.y -= g->MuzzleHeight / Z_FACTOR; const double radians = dir2radians[a->direction] + g->AngleOffset; const int range = GunGetRange(g); color_t color = colorCyan; color.a = 64; const double spreadHalf = (g->Spread.Count - 1) * g->Spread.Width / 2 + g->Recoil / 2; if (spreadHalf > 0) { DrawLaserSightSingle(muzzlePos, radians - spreadHalf, range, color); DrawLaserSightSingle(muzzlePos, radians + spreadHalf, range, color); } else { DrawLaserSightSingle(muzzlePos, radians, range, color); } }
int AIGoto(TActor *actor, Vec2i p, bool ignoreObjects) { Vec2i a = Vec2iFull2Real(actor->Pos); Vec2i currentTile = Vec2iToTile(a); Vec2i goalTile = Vec2iToTile(p); AIGotoContext *c = &actor->aiContext->Goto; // If we are already there, bail // This can happen if AI is trying to track the player, // but the player has died, for example. if (Vec2iEqual(currentTile, goalTile)) { return 0; } // If we are currently following an A* path, // and it is still valid, keep following it until // we have reached a new tile if (c && c->IsFollowing && AStarCloseToPath(c, currentTile, goalTile)) { return AStarFollow(c, currentTile, &actor->tileItem, a); } else if (AIHasClearPath(a, p, ignoreObjects)) { // Simple case: if there's a clear line between AI and target, // walk straight towards it return AIGotoDirect(a, p); } else { // We need to recalculate A* AStarContext ac; ac.Map = &gMap; ac.IsTileOk = ignoreObjects ? IsTileWalkable : IsTileWalkableAroundObjects; // First, if the goal tile is blocked itself, // find a nearby tile that can be walked to c->Goal = MapSearchTileAround(ac.Map, goalTile, ac.IsTileOk); c->PathIndex = 1; // start navigating to the next path node ASPathDestroy(c->Path); c->Path = ASPathCreate( &cPathNodeSource, &ac, ¤tTile, &c->Goal); // In case we can't calculate A* for some reason, // try simple navigation again if (ASPathGetCount(c->Path) <= 1) { debug( D_MAX, "Error: can't calculate path from {%d, %d} to {%d, %d}", currentTile.x, currentTile.y, goalTile.x, goalTile.y); return AIGotoDirect(a, p); } return AStarFollow(c, currentTile, &actor->tileItem, a); } }
TObject *AIGetObjectRunningInto(TActor *a, int cmd) { // Check the position just in front of the character; // check if there's a (non-dangerous) object in front of it Vec2i frontPos = Vec2iFull2Real(a->Pos); TTileItem *item; if (cmd & CMD_LEFT) { frontPos.x--; } else if (cmd & CMD_RIGHT) { frontPos.x++; } if (cmd & CMD_UP) { frontPos.y--; } else if (cmd & CMD_DOWN) { frontPos.y++; } item = GetItemOnTileInCollision( &a->tileItem, frontPos, TILEITEM_IMPASSABLE, CalcCollisionTeam(1, a), gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT); if (!item || item->kind != KIND_OBJECT) { return NULL; } return CArrayGet(&gObjs, item->id); }
void WeaponUpdate( Weapon *w, const int ticks, const Vec2i fullPos, const direction_e d) { // Reload sound if (ConfigGetBool(&gConfig, "Sound.Reloads") && w->lock > w->Gun->ReloadLead && w->lock - ticks <= w->Gun->ReloadLead && w->lock > 0 && w->Gun->ReloadSound != NULL) { SoundPlayAtPlusDistance( &gSoundDevice, w->Gun->ReloadSound, Vec2iFull2Real(fullPos), RELOAD_DISTANCE_PLUS); // Brass shells if (w->Gun->Brass) { AddBrass(w->Gun, d, fullPos); } } w->lock -= ticks; if (w->lock < 0) { w->lock = 0; } w->soundLock -= ticks; if (w->soundLock < 0) { w->soundLock = 0; } w->clickLock -= ticks; if (w->clickLock < 0) { w->clickLock = 0; } if (w->stateCounter >= 0) { w->stateCounter = MAX(0, w->stateCounter - ticks); if (w->stateCounter == 0) { switch (w->state) { case GUNSTATE_FIRING: WeaponSetState(w, GUNSTATE_RECOIL); break; case GUNSTATE_RECOIL: WeaponSetState(w, GUNSTATE_READY); break; default: assert(0); break; } } } }
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 AddObjectOld( int x, int y, Vec2i size, const TOffsetPic * pic, PickupType type, int tileFlags) { TObject *o = CArrayGet(&gObjs, ObjAdd( Vec2iNew(x, y), size, NULL, type, tileFlags)); o->pic = pic; o->wreckedPic = NULL; o->structure = 0; o->flags = 0; MapTryMoveTileItem(&gMap, &o->tileItem, Vec2iFull2Real(Vec2iNew(x, y))); }
Vec2i AIGetClosestPlayerPos(Vec2i pos) { TActor *closestPlayer = AIGetClosestPlayer(pos); if (closestPlayer) { return Vec2iFull2Real(closestPlayer->Pos); } else { return pos; } }
static bool IsPosOK(TActor *actor, Vec2i pos) { const Vec2i realPos = Vec2iFull2Real(pos); if (IsCollisionDiamond(&gMap, realPos, actor->tileItem.size)) { return false; } if (CollideGetFirstItem( &actor->tileItem, realPos, TILEITEM_IMPASSABLE, CalcCollisionTeam(1, actor), IsPVP(gCampaign.Entry.Mode))) { return false; } return true; }
static bool IsPosOK(TActor *actor, Vec2i pos) { Vec2i realPos = Vec2iFull2Real(pos); Vec2i size = Vec2iNew(actor->tileItem.w, actor->tileItem.h); if (IsCollisionWallOrEdge(&gMap, realPos, size)) { return 0; } if (GetItemOnTileInCollision( &actor->tileItem, realPos, TILEITEM_IMPASSABLE, CalcCollisionTeam(1, actor), gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT)) { return 0; } return 1; }
int ObjAdd( Vec2i pos, Vec2i size, const char *picName, PickupType type, int tileFlags) { // Find an empty slot in actor list TObject *o = NULL; int i; for (i = 0; i < (int)gObjs.size; i++) { TObject *obj = CArrayGet(&gObjs, i); if (!obj->isInUse) { o = obj; break; } } if (o == NULL) { TObject obj; memset(&obj, 0, sizeof obj); CArrayPushBack(&gObjs, &obj); i = (int)gObjs.size - 1; o = CArrayGet(&gObjs, i); } memset(o, 0, sizeof *o); o->pic = NULL; o->wreckedPic = NULL; o->picName = picName; o->Type = type; o->structure = 0; o->flags = 0; o->tileItem.x = o->tileItem.y = -1; o->tileItem.flags = tileFlags; o->tileItem.kind = KIND_OBJECT; o->tileItem.getPicFunc = GetObjectPic; o->tileItem.getActorPicsFunc = NULL; o->tileItem.w = size.x; o->tileItem.h = size.y; o->tileItem.id = i; MapTryMoveTileItem(&gMap, &o->tileItem, Vec2iFull2Real(pos)); o->isInUse = true; return i; }
int MobObjAdd(const Vec2i fullpos, const int player, const int uid) { // Find an empty slot in mobobj list TMobileObject *obj = NULL; int i; for (i = 0; i < (int)gMobObjs.size; i++) { TMobileObject *m = CArrayGet(&gMobObjs, i); if (!m->isInUse) { obj = m; break; } } if (obj == NULL) { TMobileObject m; memset(&m, 0, sizeof m); CArrayPushBack(&gMobObjs, &m); i = (int)gMobObjs.size - 1; obj = CArrayGet(&gMobObjs, i); } memset(obj, 0, sizeof *obj); obj->x = fullpos.x; obj->y = fullpos.y; obj->player = player; obj->uid = uid; obj->tileItem.kind = KIND_MOBILEOBJECT; obj->tileItem.id = i; obj->soundLock = 0; obj->isInUse = true; obj->tileItem.x = obj->tileItem.y = -1; obj->tileItem.getPicFunc = NULL; obj->tileItem.getActorPicsFunc = NULL; obj->tileItem.drawFunc = (TileItemDrawFunc)BogusDraw; obj->tileItem.drawData.MobObjId = i; obj->updateFunc = UpdateMobileObject; MapTryMoveTileItem(&gMap, &obj->tileItem, Vec2iFull2Real(fullpos)); return i; }
TActor *AIGetClosestPlayer(Vec2i fullpos) { int i; int minDistance = -1; TActor *closestPlayer = NULL; for (i = 0; i < gOptions.numPlayers; i++) { if (IsPlayerAlive(i)) { TActor *p = CArrayGet(&gActors, gPlayerIds[i]); Vec2i pPos = Vec2iFull2Real(p->Pos); int distance = CHEBYSHEV_DISTANCE( fullpos.x, fullpos.y, pPos.x, pPos.y); if (!closestPlayer || distance < minDistance) { closestPlayer = p; minDistance = distance; } } } return closestPlayer; }
static void DrawLaserSight(const TActor *a, const Vec2i picPos) { // Draw weapon indicators const GunDescription *g = ActorGetGun(a)->Gun; Vec2i muzzlePos = Vec2iAdd( picPos, Vec2iFull2Real(GunGetMuzzleOffset(g, a->direction))); muzzlePos.y -= g->MuzzleHeight / Z_FACTOR; const double radians = dir2radians[a->direction] + g->AngleOffset; const int range = GunGetRange(g); color_t color = colorCyan; color.a = 64; const double spreadHalf = (g->Spread.Count - 1) * g->Spread.Width / 2 + g->Recoil / 2; if (spreadHalf > 0) { DrawLaserSightSingle(muzzlePos, radians - spreadHalf, range, color); DrawLaserSightSingle(muzzlePos, radians + spreadHalf, range, color); } else { DrawLaserSightSingle(muzzlePos, radians, range, color); } }
bool HitItem(TMobileObject *obj, const Vec2i pos, const bool multipleHits) { // Don't hit if no damage dealt // This covers non-damaging debris explosions if (obj->bulletClass->Power <= 0 && obj->bulletClass->Special == SPECIAL_NONE) { return 0; } // Get all items that collide HitItemData data; data.HasHit = false; data.MultipleHits = multipleHits; data.HasFirstCollision = false; data.Obj = obj; CollideAllItems( &obj->tileItem, Vec2iFull2Real(pos), TILEITEM_CAN_BE_SHOT, COLLISIONTEAM_NONE, gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT, HitItemFunc, &data); return data.HasHit; }
static HitType HitItem( TMobileObject *obj, const Vec2i pos, const bool multipleHits) { // Don't hit if no damage dealt // This covers non-damaging debris explosions if (obj->bulletClass->Power <= 0 && (obj->specialLock > 0 || obj->bulletClass->Special == SPECIAL_NONE)) { return HIT_NONE; } // Get all items that collide HitItemData data; data.HitType = HIT_NONE; data.MultipleHits = multipleHits; data.Obj = obj; CollideTileItems( &obj->tileItem, Vec2iFull2Real(pos), TILEITEM_CAN_BE_SHOT, COLLISIONTEAM_NONE, IsPVP(gCampaign.Entry.Mode), HitItemFunc, &data); return data.HitType; }
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; }
void BulletAdd(const NAddBullet add) { const Vec2i pos = Net2Vec2i(add.MuzzlePos); // Find an empty slot in mobobj list TMobileObject *obj = NULL; int i; for (i = 0; i < (int)gMobObjs.size; i++) { TMobileObject *m = CArrayGet(&gMobObjs, i); if (!m->isInUse) { obj = m; break; } } if (obj == NULL) { TMobileObject m; memset(&m, 0, sizeof m); CArrayPushBack(&gMobObjs, &m); i = (int)gMobObjs.size - 1; obj = CArrayGet(&gMobObjs, i); } memset(obj, 0, sizeof *obj); obj->UID = add.UID; obj->bulletClass = StrBulletClass(add.BulletClass); obj->x = pos.x; obj->y = pos.y; obj->z = add.MuzzleHeight; obj->dz = add.Elevation; obj->vel = Vec2iFull2Real(Vec2iScale( GetFullVectorsForRadians(add.Angle), RAND_INT(obj->bulletClass->SpeedLow, obj->bulletClass->SpeedHigh))); if (obj->bulletClass->SpeedScale) { obj->vel.y = obj->vel.y * TILE_WIDTH / TILE_HEIGHT; } obj->PlayerUID = add.PlayerUID; obj->ActorUID = add.ActorUID; obj->range = RAND_INT( obj->bulletClass->RangeLow, obj->bulletClass->RangeHigh); obj->flags = add.Flags; if (obj->bulletClass->HurtAlways) { obj->flags |= FLAGS_HURTALWAYS; } obj->tileItem.kind = KIND_MOBILEOBJECT; obj->tileItem.id = i; obj->isInUse = true; obj->tileItem.x = obj->tileItem.y = -1; obj->tileItem.getPicFunc = NULL; obj->tileItem.getActorPicsFunc = NULL; obj->tileItem.drawFunc = NULL; obj->tileItem.drawData.MobObjId = i; obj->tileItem.CPic = obj->bulletClass->CPic; obj->tileItem.CPicFunc = GetBulletDrawContext; obj->tileItem.size = obj->bulletClass->Size; obj->tileItem.ShadowSize = obj->bulletClass->ShadowSize; obj->updateFunc = UpdateBullet; MapTryMoveTileItem(&gMap, &obj->tileItem, Vec2iFull2Real(pos)); }
// Draw player's score, health etc. static void DrawPlayerStatus( HUD *hud, const PlayerData *data, const TActor *p, const int flags, const Rect2i r) { if (p != NULL) { DrawObjectiveCompass( hud->device, Vec2iFull2Real(p->Pos), r, hud->showExit); } Vec2i pos = Vec2iNew(5, 5); FontOpts opts = FontOptsNew(); if (flags & HUDFLAGS_PLACE_RIGHT) { opts.HAlign = ALIGN_END; } if (flags & HUDFLAGS_PLACE_BOTTOM) { opts.VAlign = ALIGN_END; pos.y += BOTTOM_PADDING; } opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; FontStrOpt(data->name, Vec2iZero(), opts); const int rowHeight = 1 + FontH(); pos.y += rowHeight; char s[50]; if (IsScoreNeeded(gCampaign.Entry.Mode)) { if (ConfigGetBool(&gConfig, "Game.Ammo")) { // Display money instead of ammo sprintf(s, "Cash: $%d", data->score); } else { sprintf(s, "Score: %d", data->score); } } else { s[0] = 0; } if (p) { // Score/money opts.Pad = pos; FontStrOpt(s, Vec2iZero(), opts); // Health pos.y += rowHeight; DrawHealth(hud->device, p, pos, opts.HAlign, opts.VAlign); // Lives pos.y += rowHeight; DrawLives(hud->device, data, pos, opts.HAlign, opts.VAlign); // Weapon pos.y += rowHeight + LIVES_ROW_EXTRA_Y; DrawWeaponStatus(hud, p, pos, opts.HAlign, opts.VAlign); } else { opts.Pad = pos; FontStrOpt(s, Vec2iZero(), opts); } if (ConfigGetBool(&gConfig, "Interface.ShowHUDMap") && !(flags & HUDFLAGS_SHARE_SCREEN) && IsAutoMapEnabled(gCampaign.Entry.Mode)) { DrawRadar(hud->device, p, RADAR_SCALE, flags, hud->showExit); } }
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; } }