From 590805e3bfc21909b756bd7b671d6f1de9ce9393 Mon Sep 17 00:00:00 2001 From: joris <joris.dauphin@gmail.com> Date: Sat, 18 Feb 2012 13:41:14 +0100 Subject: [PATCH] [+]New missile class, "missile-class-tracer", this missile will seek towards the target. [+]New missile class, "missile-class-clip-to-target", this missile will remains clipped to target unit and will play it's animation once, then target will take the damage. [+]Added CorrectSphashDamage flag in missiles for splash damages, you can use it to prevent damaging other units, which terrain type didn't match the missile's target's unit's terrain type (e.g. don't damage flyers if missile is targeted to land unit) Patch from Cybermind --- src/include/missile.h | 21 +++- src/stratagus/missile.cpp | 192 +++++++++++++++++++++---------- src/stratagus/script_missile.cpp | 6 +- 3 files changed, 157 insertions(+), 62 deletions(-) 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); }