logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic Issue #16. Defects in the implementation of UPak.Cloak (Read 187 times)
Masterkent
Developer Team
Online



Posts: 1040
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Issue #16. Defects in the implementation of UPak.Cloak
Mar 3rd, 2018 at 9:55pm
Print Post  
#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 <= 1)
		JumpZAdjustment = 1.5;
}

event float BotDesireability(Pawn Bot)
{
	local Cloak AlreadyHas;

	AlreadyHas = Cloak(Bot.FindInventoryType(class));
	if (AlreadyHas == none)
		return MaxDesireability;
	else
		return 0;
}

function PickupFunction(Pawn Other)
{
	if (Other.IsA('Bots'))
	{
		// Small chance of activating on Pickup, else it heads to Idle2 state.
		if (FRand() < 0.13)
			GotoState('Activated');
	}
}

// ================================================================================================
// Activated State
// ================================================================================================

state Activated
{
	function BeginState()
	{
		bActive = true;
		DisableCloakAmbientSound();
		Enable('Tick');

		// new sound stuff:
		Owner.PlaySound(sound'CloakOn');

		CloakOwner();

		// "Turn on" the bEngaging part of the Tick function below..
		bEngaging = true;

		// Set their current weapon to be invisible
		if (Pawn(Owner).Weapon != none)
			InvisWeapons(Pawn(Owner).Weapon);

		SetTimer(0.7, true);
	}

	function EndState()
	{
		bActive = false;
	}

	function Activate()
	{
		if (!Level.Game.IsA('CloakMatch'))
			super.Activate();
	}

	function Timer()
	{
		local Bots SP;

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

		EnableCloakAmbientSound();

		if (!Level.Game.IsA('CloakMatch'))
			Charge--;
		foreach AllActors(class'Bots', SP)
		{
			if (SP.Enemy == Owner)
			{
				SP.Enemy = none;
				SP.Target = none;
			}
		}

		// If the charge is <= 0, use the bEngaging routine in TICK for the flicker effect again..
		// Run the bEngaging routine for about 5 seconds, then destroy it.
		if (Charge <= 0)
		{
			if (++CountDown >= 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.14 )
			{
				OwnerPawn.ScaleGlow = 0.4;
				LastOwnerRotation = OwnerPawn.Rotation;
			}
			else if (VSize(OwnerPawn.Velocity) == 0 && 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].
  
Back to top
 
IP Logged
 
Smirftsch
Forum Administrator
*****
Offline



Posts: 7686
Location: at home
Joined: Apr 30th, 1998
Gender: Male
Re: Issue #16. Defects in the implementation of UPak.Cloak
Reply #1 - Mar 4th, 2018 at 8:28am
Print Post  
good job!
  

Sometimes you have to lose a fight to win the war.
Back to top
WWWICQ  
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo