// Fast projectiles -------------------------------------------------------- class FastProjectile : Actor { Default { Projectile; MissileHeight 0; } virtual void Effect() { class trail = MissileName; if (trail != null) { double hitz = pos.z - 8; if (hitz < floorz) { hitz = floorz; } // Do not clip this offset to the floor. hitz += MissileHeight; Actor act = Spawn (trail, (pos.xy, hitz), ALLOW_REPLACE); if (act != null) { if (bGetOwner && target != null) act.target = target; else act.target = self; act.angle = angle; act.pitch = pitch; } } } //---------------------------------------------------------------------------- // // AFastProjectile :: Tick // // Thinker for the ultra-fast projectiles used by Heretic and Hexen // //---------------------------------------------------------------------------- override void Tick () { ClearInterpolation(); double oldz = pos.Z; if (!bNoTimeFreeze) { //Added by MC: Freeze mode. if (globalfreeze || level.Frozen) { return; } } // [RH] Ripping is a little different than it was in Hexen FCheckPosition tm; tm.DoRipping = bRipper; int count = 8; if (radius > 0) { while (abs(Vel.X) >= radius * count || abs(Vel.Y) >= radius * count) { // we need to take smaller steps. count += count; } } if (height > 0) { while (abs(Vel.Z) >= height * count) { count += count; } } // Handle movement bool ismoved = Vel != (0, 0, 0) // Check Z position set during previous tick. // It should be strictly equal to the argument of SetZ() function. || ( (pos.Z != floorz ) /* Did it hit the floor? */ && (pos.Z != ceilingz - Height) /* Did it hit the ceiling? */ ); if (ismoved) { // force some lateral movement so that collision detection works as intended. if (bMissile && Vel.X == 0 && Vel.Y == 0 && !IsZeroDamage()) { Vel.X = MinVel; } Vector3 frac = Vel / count; int changexy = frac.X != 0 || frac.Y != 0; int ripcount = count / 8; for (int i = 0; i < count; i++) { if (changexy) { if (--ripcount <= 0) { tm.ClearLastRipped(); // [RH] Do rip damage each step, like Hexen } if (!TryMove (Pos.XY + frac.XY, true, NULL, tm)) { // Blocked move if (!bSkyExplode) { let l = tm.ceilingline; if (l && l.backsector && l.backsector.GetTexture(sector.ceiling) == skyflatnum) { let posr = PosRelative(l.backsector); if (pos.Z >= l.backsector.ceilingplane.ZatPoint(posr.XY)) { // Hack to prevent missiles exploding against the sky. // Does not handle sky floors. Destroy (); return; } } // [RH] Don't explode on horizon lines. if (BlockingLine != NULL && BlockingLine.special == Line_Horizon) { Destroy (); return; } } ExplodeMissile (BlockingLine, BlockingMobj); return; } } AddZ(frac.Z); UpdateWaterLevel (); oldz = pos.Z; if (oldz <= floorz) { // Hit the floor if (floorpic == skyflatnum && !bSkyExplode) { // [RH] Just remove the missile without exploding it // if this is a sky floor. Destroy (); return; } SetZ(floorz); HitFloor (); Destructible.ProjectileHitPlane(self, SECPART_Floor); ExplodeMissile (NULL, NULL); return; } if (pos.Z + height > ceilingz) { // Hit the ceiling if (ceilingpic == skyflatnum && !bSkyExplode) { Destroy (); return; } SetZ(ceilingz - Height); Destructible.ProjectileHitPlane(self, SECPART_Ceiling); ExplodeMissile (NULL, NULL); return; } CheckPortalTransition(); if (changexy && ripcount <= 0) { ripcount = count >> 3; // call the 'Effect' method. Effect(); } } } if (!CheckNoDelay()) return; // freed itself // Advance the state if (tics != -1) { if (tics > 0) tics--; while (!tics) { if (!SetState (CurState.NextState)) { // mobj was removed return; } } } } }