diff --git a/src/include/missile.h b/src/include/missile.h index ba746b63e..b7d93680a 100644 --- a/src/include/missile.h +++ b/src/include/missile.h @@ -78,7 +78,7 @@ ** ** MissileType::Transparency ** -** Set a missile transparency. Current supported value is 50 only. +** Set a missile transparency. ** ** MissileType::FiredSound ** @@ -371,13 +371,17 @@ enum _missile_class_ { /// Missile surround x,y MissileClassFlameShield, /// Missile is death coil. - MissileClassDeathCoil + MissileClassDeathCoil, + /// Missile seeks towards to target unit + MissileClassTracer, + /// Missile remains clipped to target's current goal and plays his animation once + MissileClassClipToTarget }; /// Base structure of missile-types class MissileType { public: - MissileType(const std::string &ident); + explicit MissileType(const std::string &ident); ~MissileType(); /// load the graphics for a missile type @@ -404,6 +408,7 @@ public: SoundConfig FiredSound; /// fired sound SoundConfig ImpactSound; /// impact sound for this missile-type + bool CorrectSphashDamage; /// restricts the radius damage depending on land, air, naval bool Flip; /// flip image when facing left bool CanHitOwner; /// missile can hit the owner bool FriendlyFire; /// missile can't hit own units @@ -552,6 +557,16 @@ public: virtual void Action(); }; +class MissileTracer : public Missile { +public: + virtual void Action(); +}; + +class MissileClipToTarget : public Missile { +public: + virtual void Action(); +}; + class BurningBuildingFrame { public: BurningBuildingFrame() : Percent(0), Missile(NULL) {}; diff --git a/src/stratagus/missile.cpp b/src/stratagus/missile.cpp index 163bab70d..9aa2cce0f 100644 --- a/src/stratagus/missile.cpp +++ b/src/stratagus/missile.cpp @@ -216,6 +216,12 @@ Missile *Missile::Init(MissileType *mtype, const PixelPos &startPos, const Pixel case MissileClassDeathCoil : missile = new MissileDeathCoil; break; + case MissileClassTracer : + missile = new MissileTracer; + break; + case MissileClassClipToTarget : + missile = new MissileClipToTarget; + break; } const PixelPos halfSize = mtype->size / 2; missile->position = startPos - halfSize; @@ -464,15 +470,15 @@ void FireMissile(CUnit &unit) ** ** @return sx,sy,ex,ey defining area in Map */ -static void GetMissileMapArea(const Missile *missile, Vec2i& boxMin, Vec2i &boxMax) +static void GetMissileMapArea(const Missile &missile, Vec2i& boxMin, Vec2i &boxMax) { #define BoundX(x) std::min<int>(std::max<int>(0, x), Map.Info.MapWidth - 1) #define BoundY(y) std::min<int>(std::max<int>(0, y), Map.Info.MapHeight - 1) - boxMin.x = BoundX(missile->position.x / PixelTileSize.x); - boxMin.y = BoundY(missile->position.y / PixelTileSize.y); - boxMax.x = BoundX((missile->position.x + missile->Type->Width() + PixelTileSize.x - 1) / PixelTileSize.x); - boxMax.y = BoundY((missile->position.y + missile->Type->Height() + PixelTileSize.y - 1) / PixelTileSize.y); + boxMin.x = BoundX(missile.position.x / PixelTileSize.x); + boxMin.y = BoundY(missile.position.y / PixelTileSize.y); + boxMax.x = BoundX((missile.position.x + missile.Type->Width() + PixelTileSize.x - 1) / PixelTileSize.x); + boxMax.y = BoundY((missile.position.y + missile.Type->Height() + PixelTileSize.y - 1) / PixelTileSize.y); #undef BoundX #undef BoundY @@ -486,13 +492,13 @@ static void GetMissileMapArea(const Missile *missile, Vec2i& boxMin, Vec2i &boxM ** ** @return Returns true if visible, false otherwise. */ -static int MissileVisibleInViewport(const CViewport *vp, const Missile *missile) +static int MissileVisibleInViewport(const CViewport &vp, const Missile &missile) { Vec2i boxmin; Vec2i boxmax; GetMissileMapArea(missile, boxmin, boxmax); - if (!vp->AnyMapAreaVisibleInViewport(boxmin.x, boxmin.y, boxmax.x, boxmax.y)) { + if (!vp.AnyMapAreaVisibleInViewport(boxmin.x, boxmin.y, boxmax.x, boxmax.y)) { return 0; } Vec2i pos; @@ -632,23 +638,23 @@ int FindAndSortMissiles(const CViewport *vp, // Loop through global missiles, then through locals. // for (i = GlobalMissiles.begin(); i != GlobalMissiles.end() && nmissiles < tablesize; ++i) { - Missile *missile = (*i); - if (missile->Delay || missile->Hidden) { + Missile &missile = *(*i); + if (missile.Delay || missile.Hidden) { continue; // delayed or hidden -> aren't shown } // Draw only visible missiles - if (MissileVisibleInViewport(vp, missile)) { - table[nmissiles++] = missile; + if (MissileVisibleInViewport(*vp, missile)) { + table[nmissiles++] = &missile; } } for (i = LocalMissiles.begin(); i != LocalMissiles.end() && nmissiles < tablesize; ++i) { - Missile *missile = (*i); - if (missile->Delay || missile->Hidden) { + Missile &missile = *(*i); + if (missile.Delay || missile.Hidden) { continue; // delayed or hidden -> aren't shown } // Local missile are visible. - table[nmissiles++] = missile; + table[nmissiles++] = &missile; } if (nmissiles > 1) { std::sort(table, table + nmissiles, MissileDrawLevelCompare); @@ -763,6 +769,36 @@ static int PointToPointMissile(Missile &missile) return 0; } +/** +** Handle tracer missile. +** +** @param missile Missile pointer. +** +** @return 1 if goal is reached, 0 else. +*/ +static int TracerMissile(Missile &missile) +{ + if (MissileInitMove(missile) == 1) { + return 1; + } + + Assert(missile.Type != NULL); + Assert(missile.TargetUnit != NULL); + Assert(missile.TotalStep != 0); + + missile.destination.x = missile.TargetUnit->tilePos.x * PixelTileSize.x + missile.TargetUnit->IX; + missile.destination.y = missile.TargetUnit->tilePos.y * PixelTileSize.y + missile.TargetUnit->IY; + + const PixelPos diff = (missile.destination - missile.source); + missile.position = missile.source + diff * missile.CurrentStep / missile.TotalStep; + + if (missile.Type->SmokeMissile && missile.CurrentStep) { + const PixelPos position = missile.position + missile.Type->size / 2; + MakeMissile(missile.Type->SmokeMissile, position, position); + } + return 0; +} + /** ** Calculate parabolic trajectories. ** @@ -864,27 +900,29 @@ static void MissileHitsWall(const Missile &missile, const Vec2i &tilePos, int sp ** ** @param missile Missile reaching end-point. */ -void MissileHit(Missile *missile) +static void MissileHit(Missile &missile) { - if (missile->Type->ImpactSound.Sound) { - PlayMissileSound(missile, missile->Type->ImpactSound.Sound); + const MissileType &mtype = *missile.Type; + + if (mtype.ImpactSound.Sound) { + PlayMissileSound(&missile, mtype.ImpactSound.Sound); } - const PixelPos pixelPos = missile->position + missile->Type->size / 2; + const PixelPos pixelPos = missile.position + missile.Type->size / 2; // // The impact generates a new missile. // - if (missile->Type->ImpactMissile) { - MakeMissile(missile->Type->ImpactMissile, pixelPos, pixelPos); + if (mtype.ImpactMissile) { + MakeMissile(mtype.ImpactMissile, pixelPos, pixelPos); } - if (missile->Type->ImpactParticle) { - missile->Type->ImpactParticle->pushPreamble(); - missile->Type->ImpactParticle->pushInteger(pixelPos.x); - missile->Type->ImpactParticle->pushInteger(pixelPos.y); - missile->Type->ImpactParticle->run(); + if (mtype.ImpactParticle) { + mtype.ImpactParticle->pushPreamble(); + mtype.ImpactParticle->pushInteger(pixelPos.x); + mtype.ImpactParticle->pushInteger(pixelPos.y); + mtype.ImpactParticle->run(); } - if (!missile->SourceUnit) { // no owner - green-cross ... + if (!missile.SourceUnit) { // no owner - green-cross ... return; } @@ -899,21 +937,22 @@ void MissileHit(Missile *missile) // // Choose correct goal. // - if (!missile->Type->Range) { - if (missile->TargetUnit) { + if (!mtype.Range) { + if (missile.TargetUnit && (mtype.FriendlyFire == false + || missile.TargetUnit->Player->Index != missile.SourceUnit->Player->Index)) { // // Missiles without range only hits the goal always. // - CUnit &goal = *missile->TargetUnit; + CUnit &goal = *missile.TargetUnit; if (goal.Destroyed) { // Destroyed goal.RefsDecrease(); - missile->TargetUnit = NoUnitP; + missile.TargetUnit = NoUnitP; return; } - MissileHitsGoal(*missile, goal, 1); + MissileHitsGoal(missile, goal, 1); return; } - MissileHitsWall(*missile, pos, 1); + MissileHitsWall(missile, pos, 1); return; } @@ -921,42 +960,48 @@ void MissileHit(Missile *missile) // // Hits all units in range. // - const int range = missile->Type->Range; + const int range = mtype.Range; CUnit *table[UnitMax]; const int n = Map.Select(pos.x - range + 1, pos.y - range + 1, pos.x + range, pos.y + range, table); - Assert(missile->SourceUnit != NULL); + Assert(missile.SourceUnit != NULL); for (int i = 0; i < n; ++i) { CUnit &goal = *table[i]; // // Can the unit attack the this unit-type? // NOTE: perhaps this should be come a property of the missile. + // Also check CorrectSphashDamage so land explosions can't hit the air units // - if (CanTarget(missile->SourceUnit->Type, goal.Type)) { + if (CanTarget(missile.SourceUnit->Type, goal.Type) + && (mtype.CorrectSphashDamage == false + || goal.Type->UnitType == missile.TargetUnit->Type->UnitType) + && (mtype.FriendlyFire == false + || (missile.TargetUnit->Player->Index != missile.SourceUnit->Player->Index))) { int splash = goal.MapDistanceTo(pos.x, pos.y); + if (splash) { - splash *= missile->Type->SplashFactor; + splash *= mtype.SplashFactor; } else { splash = 1; } - MissileHitsGoal(*missile, goal, splash); + MissileHitsGoal(missile, goal, splash); } } } // Missile hits ground. - const Vec2i offset = { missile->Type->Range, missile->Type->Range}; + const Vec2i offset = { mtype.Range, mtype.Range}; const Vec2i posmin = pos - offset; - for (int i = missile->Type->Range * 2; --i;) { - for (int j = missile->Type->Range * 2; --j;) { + for (int i = mtype.Range * 2; --i;) { + for (int j = mtype.Range * 2; --j;) { const Vec2i posIt = {posmin.x + i, posmin.y + j}; if (Map.Info.IsPointOnMap(posIt)) { int d = MapDistance(pos, posIt); - d *= missile->Type->SplashFactor; + d *= mtype.SplashFactor; if (d == 0) { d = 1; } - MissileHitsWall(*missile, posIt, d); + MissileHitsWall(missile, posIt, d); } } } @@ -1239,7 +1284,7 @@ void InitMissileTypes() MissileType::MissileType(const std::string &ident) : Ident(ident), Transparency(0), DrawLevel(0), SpriteFrames(0), NumDirections(0), - Flip(false), CanHitOwner(false), FriendlyFire(false), + CorrectSphashDamage(false), Flip(false), CanHitOwner(false), FriendlyFire(false), Class(), NumBounces(0), StartDelay(0), Sleep(0), Speed(0), Range(0), SplashFactor(0), ImpactMissile(NULL), SmokeMissile(NULL), ImpactParticle(NULL), G(NULL) @@ -1337,7 +1382,7 @@ void MissilePointToPoint::Action() { this->Wait = this->Type->Sleep; if (PointToPointMissile(*this)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } else { NextMissileFrame(*this, 1, 0); @@ -1353,7 +1398,7 @@ void MissilePointToPointWithHit::Action() this->Wait = this->Type->Sleep; if (PointToPointMissile(*this)) { if (NextMissileFrame(*this, 1, 0)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } } @@ -1366,7 +1411,7 @@ void MissilePointToPointCycleOnce::Action() { this->Wait = this->Type->Sleep; if (PointToPointMissile(*this)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } else { NextMissileFrameCycle(*this); @@ -1380,7 +1425,7 @@ void MissileStay::Action() { this->Wait = this->Type->Sleep; if (NextMissileFrame(*this, 1, 0)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } } @@ -1400,11 +1445,11 @@ void MissilePointToPointBounce::Action() this->source = this->position; PointToPointMissile(*this); //this->State++; - MissileHit(this); + MissileHit(*this); // FIXME: hits to left and right // FIXME: reduce damage effects on later impacts } else { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } } else { @@ -1431,7 +1476,7 @@ void MissileCycleOnce::Action() break; case 3: if (NextMissileFrame(*this, -1, 0)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } break; @@ -1475,7 +1520,7 @@ void MissileHit::Action() { this->Wait = this->Type->Sleep; if (PointToPointMissile(*this)) { - ::MissileHit(this); + ::MissileHit(*this); this->TTL = 0; } } @@ -1487,7 +1532,7 @@ void MissileParabolic::Action() { this->Wait = this->Type->Sleep; if (ParabolicMissile(*this)) { - MissileHit(this); + MissileHit(*this); this->TTL = 0; } else { NextMissileFrameCycle(*this); @@ -1582,10 +1627,10 @@ void MissileLandMine::Action() { const Vec2i pos = {this->position.x / PixelTileSize.x, this->position.y / PixelTileSize.y}; - if(LandMineTargetFinder(this->SourceUnit, - this->Type->CanHitOwner).FindOnTile(Map.Field(pos)) != NULL) { + if (LandMineTargetFinder(this->SourceUnit, + this->Type->CanHitOwner).FindOnTile(Map.Field(pos)) != NULL) { DebugPrint("Landmine explosion at %d,%d.\n" _C_ pos.x _C_ pos.y); - MissileHit(this); + MissileHit(*this); this->TTL = 0; return; } @@ -1655,7 +1700,7 @@ void MissileWhirlwind::Action() missile->X _C_ missile->Y _C_ missile->TTL _C_ missile->State); #else if (!(this->TTL % CYCLES_PER_SECOND / 10)) { - MissileHit(this); + MissileHit(*this); } #endif @@ -1699,8 +1744,8 @@ void MissileDeathCoil::Action() // // Target unit still exists and casted on a special target // - if (this->TargetUnit && !this->TargetUnit->Destroyed && - this->TargetUnit->CurrentAction() == UnitActionDie) { + if (this->TargetUnit && !this->TargetUnit->Destroyed + && this->TargetUnit->CurrentAction() == UnitActionDie) { HitUnit(&source, *this->TargetUnit, this->Damage); if (source.CurrentAction() != UnitActionDie) { source.Variable[HP_INDEX].Value += this->Damage; @@ -1747,4 +1792,35 @@ void MissileDeathCoil::Action() } } +/** +** Missile flies from x,y to the target position, changing direction on the way +*/ +void MissileTracer::Action() +{ + this->Wait = this->Type->Sleep; + if (TracerMissile(*this)) { + MissileHit(*this); + this->TTL = 0; + } else { + NextMissileFrame(*this, 1, 0); + } +} + +/** +** Missile remains clipped to target's current goal and plays his animation once +*/ +void MissileClipToTarget::Action() +{ + Assert(this->TargetUnit != NULL); + + this->Wait = this->Type->Sleep; + this->position.x = this->TargetUnit->tilePos.x * PixelTileSize.x + this->TargetUnit->IX; + this->position.y = this->TargetUnit->tilePos.y * PixelTileSize.y + this->TargetUnit->IY; + + if (NextMissileFrame(*this, 1, 0)) { + MissileHit(*this); + this->TTL = 0; + } +} + //@} diff --git a/src/stratagus/script_missile.cpp b/src/stratagus/script_missile.cpp index 1fc655717..332406d68 100644 --- a/src/stratagus/script_missile.cpp +++ b/src/stratagus/script_missile.cpp @@ -71,6 +71,8 @@ static const char *MissileClassNames[] = { "missile-class-whirlwind", "missile-class-flame-shield", "missile-class-death-coil", + "missile-class-tracer", + "missile-class-clip-to-target", NULL }; @@ -135,7 +137,7 @@ static int CclDefineMissileType(lua_State *l) mtype->Flip = LuaToBoolean(l, -1); } else if (!strcmp(value, "NumDirections")) { mtype->NumDirections = LuaToNumber(l, -1); - } else if (!strcmp(value, "transparency")) { + } else if (!strcmp(value, "Transparency")) { mtype->Transparency = LuaToNumber(l, -1); } else if (!strcmp(value, "FiredSound")) { mtype->FiredSound.Name = LuaToString(l, -1); @@ -176,6 +178,8 @@ static int CclDefineMissileType(lua_State *l) mtype->FriendlyFire = LuaToBoolean(l, -1); } else if (!strcmp(value, "SplashFactor")) { mtype->SplashFactor = LuaToNumber(l, -1); + } else if (!strcmp(value, "CorrectSphashDamage")) { + mtype->CorrectSphashDamage = LuaToBoolean(l, -1); } else { LuaError(l, "Unsupported tag: %s" _C_ value); }