For direct access use https://forums.oldunreal.com
It's been quite a while since oldunreal had an overhaul, but we are moving to another server which require some updates and changes. The biggest change is the migration of our old reliable YaBB forum to phpBB. This system expects you to login with your username and old password known from YaBB.
If you experience any problems there is also the usual "password forgotten" function. Don't forget to clear your browser cache!
If you have any further concerns feel free to contact me: Smirftsch@oldunreal.com

Issue #16. Defects in the implementation of UPak.Cloak

Report bugs, read about fixes, new features and ask questions about the Unreal 227 patch here. Place comments and commit suggestions.
Post Reply
User avatar
Masterkent
OldUnreal Member
Posts: 1469
Joined: Fri Apr 05, 2013 12:41 pm

Issue #16. Defects in the implementation of UPak.Cloak

Post by Masterkent »

#16.1. Accessing none.

When a Cloaking Device is destroyed, event function Destroyed() may access none if the device has no owner:

Code: Select all

event Destroyed()
{
      Pawn( Owner ).AmbientSound = None;
      Disable( 'Tick' );
      RestoreVis();
      WeaponRestore();
      Super.Destroyed();
}
RestoreVis internally accesses none Owner too.

Suggested resolution: check for none before accessing.

#16.2. Speed of visual effects is proportional to tick rate (too high for 100 FPS and above).

Tick functions ignore DeltaTime and update owner's Fatness and ScaleGlow based on hardcoded values that do not depend on time passed between ticks. This may result in too fast visual changes when frame rate is very high.

Suggested resolution: make updates of owner's Fatness and ScaleGlow with a fixed speed corresponding to the behavior of the original implementation with frame rate of 60 FPS.

#16.3. Bad compatibility with some inventory items:

#16.3.1. The wearer becomes fully visible when his ShieldBelt is destroyed.

ShieldBelt's Destroyed() calls owner's SetDefaultDisplayProperties which resets the owner's texture and translucent style to normal values. The cloaking device does not bother to automatically make the wearer transparent again.

Suggested resolution: adjust owner's texture and transparency (along with bMeshEnviroMap) on every tick while the Cloaking Device is active.

Note: the suggested implementation (see below) should work with custom shield belts that may override Destroyed.

#16.3.2. The wearer has the default jump height after deactivation of JumpBoots.

JumpZ and AirControl are set when the Cloaking Device is activated; changes of these properties between activation and deactivation are not tracked.

Suggested resolution:

1. Set owner's JumpZ to the maximum of JumpZAdjustment and owner's JumpZ while the Cloaking Device is active.
2. Add edit-variable AirControlAdjustment with default value 6.
3. Set owner's AirControl to the maximum of AirControlAdjustment and owner's AirControl while the Cloaking Device is active. [Note: this change provides better compatibility with special boots that allow air-controlled jumps - e.g. UT_JumpBoots].

#16.3.3. Amplifier sound can disable Cloak sound and vice versa.

Energy Amplifier, Scuba Gear, and Cloaking Device use Owner's AmbientSound to play the sound of the corresponding device. So all these devices may concur for the right to play a particular sound. This implies that only one of such sounds can be played at a time.

Suggested resolution: when Cloaking Device is activated, spawn an auxiliary actor attached to the owner (by means of setting Physics to PHYS_Trailer), so that the aux actor would play the ambient sound instead of the owner.

Note 1: Cloaking Device itself cannot be used to play an ambient sound, because changing its Physics to PHYS_Trailer won't be replicated to clients.

Note 2: Although Amplifier and ScubaGear can be changed to use the same approach, this is out of scope of this particular proposal for issue #16.

Note 3: The given approach has been successfully tested on ported Botpack.UDamage (which features a specific ambient lighting) and then on modified UPak.Cloak.

#16.3.4. The wearer has the default Visibility after deactivation of Invisibility.

Cloaking Device does not track changes of Visibility between activation and deactivation.

Suggested resolution: adjust owner's Visibility on every tick while the Cloaking Device is active.

Note: New implementation should be also compatible with Botpack.UT_Invisibility. When both Cloak and UT_Invisibility are active simultaneously, the following conditions take place:

- the owner's texture is set to Invis (that is, Invis has priority over CloakTexture);

- if the owner wears a ShieldBelt or UT_SheildBelt, the visibility of the corresponding shield belt effect is determined by the implementation of UT_Invisibility (in case of my port of Botpack, UT_Invisibility will hide all shield belt effects);

- Cloaking Device sets Visibility to the minimum of the two values which would be determined by Cloaked Device and UT_Invisibility if they worked alone.

- owner's JumpZ, ScaleGlow, and Fatness are determined by the Cloaking Device.

#16.3.5. After deactivation, the device periodically resets many owner's properties to their defaults.

Cloak.Deactivated.Timer keeps changing owner's properties even after the Cloaking Device was completely disengaged.

Suggested resolution: disable the timer when the Cloaking Device stops working.

#16.4. Display properties of a cloaked weapon are not restored in time.

If an owner of an active Cloaked Device throws a weapon to another owner of an active Cloaked Device, the second owner selects the given weapon, and the first owner deactivates his Cloaking Device, then the weapon of the second owner becomes opaque.

Suggested resolution:
1. Remove the weapon from the array of affected weapons once its display properties have been restored.
2. When necessary, update weapon's display properties between activation and deactivation of the Cloaking Device.

--------------------------------------------------------------------------------

Modified code (which incorporates resolutions for all aforementioned issues):

Code: Select all

//=============================================================================
// Cloak.uc
// $Author: Deb $
// $Date: 4/23/99 12:13p $
// $Revision: 1 $
//=============================================================================
class Cloak expands Pickup;

#exec TEXTURE IMPORT NAME=I_Cloak FILE=TEXTURES\CLOAK\i_invisi.PCX GROUP="Icons"  MIPS=OFF
#exec TEXTURE IMPORT NAME=CloakTexture FILE=TEXTURES\CLOAK\CloakTexture.PCX GROUP="CloakTex"

#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakOn.WAV" NAME="CloakOn" GROUP="CloakSounds"
#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakOff.WAV" NAME="CloakOff" GROUP="CloakSounds"
#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakLoop2.WAV" NAME="CloakLoop2" GROUP="CloakSounds"
#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakLoop3.WAV" NAME="CloakLoop3" GROUP="CloakSounds"
#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakLoop1a.WAV" NAME="CloakLoop1a" GROUP="CloakSounds"
#forceexec AUDIO IMPORT FILE="Sounds\Cloak\CloakLoop1b.WAV" NAME="CloakLoop1b" GROUP="CloakSounds"
// add fade-in effect for respawning? 

var Weapon LastWeapon;                  // Last Weapon wielded by owner
var int CountDown;                        // Used in timing the "back to visible" routine
var rotator LastOwnerRotation;      // Used in checking owner's rotation vs last rotation
var bool bExpanding, bEngaging;      // Used in going invis routines (tick)
var bool bEngagingCompleted;      // Used in Timer
var float OwnerFatnessDelta;      // Used in going invis routines (tick) 
var Weapon InvisWeaponList[25];      // Array holding all affected weapons
var int ArrayCounter;                  // Counter for array
var Actor AmbientSoundActor;      // Actor that plays sound when the cloaking device is active
var() float JumpZAdjustment;
var() float AirControlAdjustment;

function PostBeginPlay()
{
      if (JumpZAdjustment = 10)
                  {
                        Pawn(Owner).ClientMessage(ItemName $ M_Deactivated);
                        Destroy();
                  }
                  else
                        bEngaging = true;      
            }
            else if (bEngaging && bEngagingCompleted)
                  GotoState(, 'Begin');
      }      

      function Tick(float DeltaTime)
      {
            local Pawn OwnerPawn;
            local bool bFlicker;
            local float TimeFactor;

            OwnerPawn = Pawn(Owner);
            if (OwnerPawn == none)
                  return;

            CloakOwner();

            if (bEngaging)
            {
                  OwnerPawn.Fatness = Rand(254);

                  if (FRand() < 0.5)
                  {
                        OwnerPawn.bMeshEnviroMap = true;
                        if (OwnerPawn.Texture == none || OwnerPawn.Texture.Name != 'Invis')
                              OwnerPawn.Texture = Texture'CloakTexture';
                        OwnerPawn.ScaleGlow = 0.08;
                  }
                  else
                  {
                        OwnerPawn.Fatness = OwnerPawn.default.Fatness;
                        if (OwnerPawn.Texture == none || OwnerPawn.Texture.Name != 'Invis')
                        {
                              OwnerPawn.bMeshEnviroMap = OwnerPawn.default.bMeshEnviroMap;
                              OwnerPawn.Texture = OwnerPawn.default.Texture;
                        }
                        OwnerPawn.ScaleGlow = 0.95;
                  }
            }
            else // Normal checks for normal invisible state. Firing, rotation, speed.
            {
                  TimeFactor = DeltaTime * 60; // 60 ticks per second is taken as the normal rate

                  OwnerPawn.bMeshEnviroMap = true;
                  if (OwnerPawn.Texture == OwnerPawn.default.Texture)
                        OwnerPawn.Texture = Texture'CloakTexture';

                  if (OwnerPawn.bFire != 0 || OwnerPawn.bAltFire != 0)
                        OwnerPawn.ScaleGlow = 0.13;

                  if (OwnerPawn.Rotation != LastOwnerRotation && OwnerPawn.ScaleGlow  0.02)
                        OwnerPawn.ScaleGlow = FMax(0.02, OwnerPawn.ScaleGlow - 0.01 * TimeFactor);      
                  else
                        bFlicker = true;

                  // Extra subtle flickering/slight expanding for effect.
                  if (bFlicker)
                  {
                        if (OwnerFatnessDelta > -14 && !bExpanding)
                        {
                              OwnerFatnessDelta = FMax(-14, OwnerFatnessDelta - 2 * TimeFactor);
                              OwnerPawn.ScaleGlow = FMax(0.01, OwnerPawn.ScaleGlow - 0.01 * TimeFactor);
                        }
                        else
                        {
                              bExpanding = true;
                              if (OwnerFatnessDelta < 0)
                              {
                                    if (VSize(OwnerPawn.Velocity) > 0)
                                    {
                                          OwnerFatnessDelta = FMin(0, OwnerFatnessDelta + 2 * TimeFactor);
                                          OwnerPawn.ScaleGlow = FMin(0.19, OwnerPawn.ScaleGlow + 0.01 * TimeFactor);
                                    }
                                    else
                                          OwnerFatnessDelta = FMin(0, OwnerFatnessDelta + TimeFactor);
                              }
                              else
                                    bExpanding = false;
                        }
                  }
                  else
                        OwnerFatnessDelta = FMin(0, OwnerFatnessDelta + 2 * TimeFactor);
                  OwnerPawn.Fatness = OwnerPawn.default.Fatness + OwnerFatnessDelta;
            }

            UpdateWeapons();
      }

      // Set a weapon to invis stats, and add it to the affected weapon array.
      function InvisWeapons(Weapon ChangeWeapon)
      {
            CloakOwnerWeapon(ChangeWeapon);
      }

      // Search the array, make sure no weapon is duplicated in it; if no match, add it to the array and
      // increment arraycounter.
      function AddWeaponToList(Weapon AddWeapon)
      {
            AddToInvisWeaponList(AddWeapon);
      }

Begin:

      bEngagingCompleted = false;
      Sleep(2.0 + FRand());
      bEngaging = false;
      bEngagingCompleted = true;
      Disable('Tick');
      OwnerFatnessDelta = 0;
      if (Pawn(Owner) != none)
      {
            Owner.Fatness = Owner.default.Fatness;
            if (Owner.Texture == none || Owner.Texture.Name != 'Invis')
                  Owner.Texture = Texture'CloakTexture';
            Owner.bMeshEnviroMap = true;
            Owner.ScaleGlow = 0.75;

            if (Pawn(Owner).Weapon != none)
            {
                  AddWeaponToList(Pawn(Owner).Weapon);
                  Pawn(Owner).Weapon.ScaleGlow = 0.75;
            }

            Sleep(0.5);
            Enable('Tick');
      }
}

function CloakOwner()
{
      local Pawn OwnerPawn;
      local float JumpZ;

      OwnerPawn = Pawn(Owner);
      if (OwnerPawn == none || OwnerPawn.bDeleteMe)
            return;

      OwnerPawn.Style = STY_Translucent;

      // Invisible to bots/scriptedpawns
      if (Level.Game.IsA('CloakMatch'))
            Pawn(Owner).Visibility = Min(100, Pawn(Owner).Visibility);
      else
            Pawn(Owner).Visibility = 0;

      if (OwnerPawn.AirControl < AirControlAdjustment)
            OwnerPawn.AirControl = AirControlAdjustment;

      JumpZ = OwnerPawn.default.JumpZ * JumpZAdjustment;
      if (OwnerPawn.JumpZ < JumpZ)
            OwnerPawn.JumpZ = JumpZ;
}

function UpdateOwnerWeapon(Weapon Weap)
{
      if (Weap == none)
            return;
      if (Weap != LastWeapon ||
            Weap.Style != STY_Translucent ||
            !Weap.bMeshEnviroMap ||
            Weap.Texture == Weap.default.Texture)
      {
            CloakOwnerWeapon(Weap);
      }
      Weap.ScaleGlow = Owner.ScaleGlow;
}

function CloakOwnerWeapon(Weapon Weap)
{
      if (Weap == none)
            return;
      Weap.Style = STY_Translucent;
      Weap.bMeshEnviroMap = true;
      Weap.Texture = Texture'CloakTexture';
      Weap.ScaleGlow = 0.12;
      AddToInvisWeaponList(Weap);
      LastWeapon = Weap;
}

function AddToInvisWeaponList(Weapon Weap)
{
      local int i;
      
      for (i = 0; i < ArrayCounter; ++i)
            if (InvisWeaponList[i] == Weap)
                  return;

      for (i = 0; i < ArrayCounter; ++i)
            if (InvisWeaponList[i] == none)
            {
                  InvisWeaponList[i] = Weap;
                  return;
            }

      if (ArrayCounter < ArrayCount(InvisWeaponList))
            InvisWeaponList[ArrayCounter++] = Weap;
}

function UpdateWeapons()
{
      local Weapon Weap;
      local int i;

      if (Pawn(Owner) == none)
            return;

      UpdateOwnerWeapon(Pawn(Owner).Weapon);
      UpdateOwnerWeapon(Pawn(Owner).PendingWeapon);

      for (i = 0; i < ArrayCounter; ++i)
            if (InvisWeaponList[i] != none &&
                  InvisWeaponList[i] != Pawn(Owner).Weapon &&
                  InvisWeaponList[i] != Pawn(Owner).PendingWeapon)
            {
                  RestoreWeaponVisibility(InvisWeaponList[i]);
            }
}

function RestoreVis()
{
      local Pawn OwnerPawn;

      OwnerPawn = Pawn(Owner);
      if (OwnerPawn == none)
            return;

      OwnerPawn.AirControl = OwnerPawn.default.AirControl;
      OwnerPawn.JumpZ = OwnerPawn.default.JumpZ * Level.Game.PlayerJumpZScaling();
      OwnerPawn.bCountJumps = false; // signal for JumpBoots to adjust JumpZ

      OwnerPawn.ScaleGlow = OwnerPawn.default.ScaleGlow;
      OwnerPawn.Fatness = OwnerPawn.default.Fatness;
      if (Owner.Texture == none || Owner.Texture.Name != 'Invis')
      {
            OwnerPawn.Style = OwnerPawn.default.Style;
            OwnerPawn.bMeshEnviroMap = OwnerPawn.default.bMeshEnviroMap;
            OwnerPawn.Visibility = OwnerPawn.default.Visibility;
      }
      if (OwnerPawn.Texture == Texture'CloakTexture')
            OwnerPawn.Texture = OwnerPawn.default.Texture;
}

function EnableCloakAmbientSound()
{
      if (AmbientSoundActor != none)
            return;

      AmbientSoundActor = Spawn(class'Triggers', Owner,, Owner.Location);
      if (AmbientSoundActor != none)
      {
            AmbientSoundActor.SetPhysics(PHYS_Trailer);
            AmbientSoundActor.RemoteRole = ROLE_SimulatedProxy;
      }
      else
            AmbientSoundActor = Owner;

      AmbientSoundActor.AmbientSound = sound'CloakLoop3';
      AmbientSoundActor.SoundRadius = 500;
      AmbientSoundActor.SoundVolume = 250;
}

function DisableCloakAmbientSound()
{
      if (Owner != none && Owner.AmbientSound == sound'CloakLoop3')
      {
            Owner.AmbientSound = none;
            Owner.SoundRadius = Owner.default.SoundRadius;
            Owner.SoundVolume = Owner.default.SoundVolume;
      }
      if (Triggers(AmbientSoundActor) != none)
            AmbientSoundActor.Destroy();
      AmbientSoundActor = none;
}

static function RestoreWeaponVisibility(out Weapon Weap)
{
      if (Weap == none || Weap.bDeleteMe)
            return;
      Weap.Texture = Weap.default.Texture;
      Weap.ScaleGlow = Weap.default.ScaleGlow;
      Weap.bMeshEnviroMap = Weap.default.bMeshEnviroMap;
      Weap.Style = Weap.default.Style;
      Weap = none;
}

function WeaponRestore()
{
      local int i;

      for (i = 0; i < ArrayCounter; ++i)
            RestoreWeaponVisibility(InvisWeaponList[i]);
      ArrayCounter = 0;
}

// Important! This makes sure that the weapon list and player are both restored when the Cloak item is destroyed.
// It's destroyed when a player dies (natural to Unreal pickup inv items) or when it's expired.

event Destroyed()
{
      DisableCloakAmbientSound();
      RestoreVis();
      WeaponRestore();
      Super.Destroyed();
}

// ================================================================================================
// Deactivated State
// ================================================================================================

state Deactivated
{
      function BeginState()
      {
            Enable('Tick');
            bEngaging = true;
            SetTimer(0.5, true);
      }
      function Timer()
      {
            CountDown++;

            if (CountDown >= 10)
            {
                  RestoreVis();
                  WeaponRestore();
                  DisableCloakAmbientSound();
                  if (Owner != none)
                        Owner.PlaySound(sound'CloakOff');
                  Disable('Tick');
                  SetTimer(0, false);
            }
            else
                  bEngaging = true;
      }
      function Tick(float DeltaTime)
      {
            local Pawn OwnerPawn;

            if (bEngaging && Pawn(Owner) != none)
            {
                  OwnerPawn = Pawn(Owner);
                  CloakOwner();

                  // Keep the weapon's scaleglow at the same level the players' is..
                  OwnerPawn.Weapon.ScaleGlow = OwnerPawn.ScaleGlow;
                  OwnerPawn.Fatness = Rand(254);

                  if (FRand() < 0.5)
                  {
                        OwnerPawn.bMeshEnviroMap = true;
                        if (OwnerPawn.Texture == none || OwnerPawn.Texture.Name != 'Invis')
                              OwnerPawn.Texture = Texture'CloakTexture';
                        OwnerPawn.ScaleGlow = 0.12;
                  }
                  else
                  {
                        OwnerPawn.Fatness = OwnerPawn.default.Fatness;
                        if (OwnerPawn.Texture == none || OwnerPawn.Texture.Name != 'Invis')
                        {
                              OwnerPawn.bMeshEnviroMap = OwnerPawn.default.bMeshEnviroMap;
                              OwnerPawn.Texture = OwnerPawn.default.Texture;
                        }
                        OwnerPawn.ScaleGlow = 0.95;
                  }

                  UpdateWeapons();
            }            
      }
}

// ================================================================================================
// Idle2 State
// ================================================================================================

// If a bot owns this, this stuff executes to determine when it activates the item
state Idle2
{
      function BeginState()
      {
            if (Pawn(Owner) != none && Owner.IsA('Bots'))
                  SetTimer(1 + FRand(), True);
      }
      
      // Scan the Radius every 1 + FRand() seconds for any other targets, if any are found, activate the item.
      function Timer()
      {
            local Pawn P;

            if (Pawn(Owner) == none)
            {
                  SetTimer(0, false);
                  return;
            }
            // Radius could use tweaking
            foreach Owner.RadiusActors(class'Pawn', P, 10000)
            {
                  if (P != Owner && P.Visibility > 0)
                  {
                        GotoState('Activated');
                        return;
                  }
            }
      }
}

//=============================================================================
// Sleeping state: Sitting hidden waiting to respawn.

state Sleeping
{
      ignores Touch;

      function BeginState()
      {
            BecomePickup();
            LightType = LT_None;
            bUnlit = false;
            bHidden = true;
      }

      function EndState()
      {
            local Pawn P;

            LightType = default.LightType;
            bUnlit = true;
            
            bSleepTouch = false;
            foreach TouchingActors(class'Pawn', P)
                  bSleepTouch = true;
      }                  
Begin:
      Sleep(ReSpawnTime);
      PlaySound(RespawnSound);
      Level.Game.PlaySpawnEffect(self);
      Sleep(0.3);
      GoToState('Pickup');
}

defaultproperties
{
      JumpZAdjustment=3.000000
      AirControlAdjustment=6.000000
      bCanActivate=True
      ExpireMessage="disengaged."
      bActivatable=True
      bDisplayableInv=True
      bAmbientGlow=False
      PickupMessage="You got the Cloaking Device."
      ItemName="Cloaking Device"
      RespawnTime=30.000000
      PickupViewMesh=LodMesh'UPak.pyramid'
      Charge=100
      PickupSound=Sound'UnrealShare.Pickups.GenPickSnd'
      Icon=Texture'UPak.Icons.I_Cloak'
      M_Activated=" engaged"
      M_Deactivated=" disengaged"
      Mesh=LodMesh'UPak.pyramid'
      SoundRadius=64
      SoundVolume=96
      CollisionRadius=19.799999
      CollisionHeight=13.700000
      LightRadius=4
      LightPeriod=5
      LightPhase=5
      LightCone=8
}
[All modifications have been tested. Modified Cloak.uc will be submitted later along with other fixes].
User avatar
Smirftsch
Administrator
Posts: 8999
Joined: Wed Apr 29, 1998 10:00 pm
Location: NaPali
Contact:

Re: Issue #16. Defects in the implementation of UPak.Cloak

Post by Smirftsch »

good job!
Sometimes you have to lose a fight to win the war.
Post Reply

Return to “Unreal 227”