User Tag List

Results 1 to 7 of 7
  1. #1
    Whicked Sick Higor's Avatar
    Join Date
    Apr 2012
    Location
    Full sail ahead!
    Posts
    3,676
    Country:

    Code review: Mines 2

    This method will use only engine events for detonations, since engine doesn't support spherical collision, it will be necessary to make an adjustment.

    We need to create a trigger with a cylinder collision that best emulates the previous approach.
    With RadiusActors as it was in the original code, an extra 17 units was added to the detection radius due to the player's CollisionRadius property, this means we can safely use the same radius as before.
    Now about collision height, a player is 78 units tall, but that doesn't guarantee that it's the height they'll reach from the floor (unlike HL's engine), this happens on ramps and if you add the fact that it is cylindric collision chances are that in these kinds of situations mines can become absurdly overpowered.

    A way to deal with this while preserving the original behaviour, is to use the trigger cylinder to catch nearby players and to then check them as it was done before (within distance + actor.CollisionRadius).
    The detonation will also use the old behaviour which is: Triggered by players, but damages both players and buildings.

    Let's first create the mine trigger which will be a serverside actor.

    sgMineTrigger.uc
    Code:
    //=============================================================================
    // sgMineTrigger.
    //
    // Written by Higor
    // Subclass of triggers to prevent translocators from bouncing from this actor
    //=============================================================================
    class sgMineTrigger expands Triggers;
    
    var Mine Master;
    
    event Touch( Actor Other )
    {
    	local pawn P;
    
    	if ( !Other.bIsPawn )
    		return;
    
    	if ( Master == none )
    	{
    		Destroy();
    		return;
    	}
    
    	if ( ScriptedPawn(Other) != none )
    	{
    		Master.CheckThis( Pawn(Other) );
    		return;
    	}
    
    	P = Pawn(Other);
    	if ( P.bIsPlayer && (P.Health > 0) && (P.PlayerReplicationInfo != None) && (P.PlayerReplicationInfo.Team != Master.Team) && !P.PlayerReplicationInfo.bIsSpectator )
    	{
    		Master.CheckThis( P);
    		return;
    	}
    }
    
    function bool IsTouching( actor Other)
    {
    	if ( Other == none )
    		return false;
    	if ( abs(Other.Location.Z - Location.Z) > (CollisionHeight + Other.CollisionHeight) )
    		return false;
    	return VSize( (Other.Location - Location) * vect(1,1,0)) < (CollisionRadius + Other.CollisionRadius);
    }
    
    defaultproperties
    {
         CollisionRadius=10.000000
         CollisionHeight=10.000000
    }
    Next post we deal with the class MINE:
    Do not post yet.

  2. #2
    Whicked Sick Higor's Avatar
    Join Date
    Apr 2012
    Location
    Full sail ahead!
    Posts
    3,676
    Country:
    Now dealing with the Mine class:

    We'll rewrite most of it, when a valid target hits the trigger it will register it into the mine until it leaves or dies.
    If a registered pawn enters blast radius and sight, detonate.

    =========
    Add these variables:
    Code:
    var Pawn CheckOn[8];
    var int iCheckOn;
    var sgMineTrigger MyTrigger;
    No need to replicate these.

    =========
    Add these functions:
    Code:
    function CheckThis( Pawn Other)
    {
    	if ( iCheckOn >= 8 )
    		return; //8 pawns on mine!!??
    
    	CheckOn[iCheckOn++] = Other;
    }
    The trigger will use this to register pawns safely.

    Code:
    function bool ShouldDetonate()
    {
    	local int i;
    	local bool bSuccess;
    
    	For ( i=iCheckOn-1 ; i>=0 ; i-- )
    	{
    		if ( !MyTrigger.IsTouching( CheckOn[i]) ) //Clean up the whole list before returning
    		{
    			CheckOn[i] = CheckOn[--iCheckOn];
    			CheckOn[iCheckOn] = none;
    			continue;
    		}
    
    		if ( !bSuccess && (VSize( CheckOn[i].Location - Location) < (75 + CheckOn[i].CollisionRadius)) && FastTrace(CheckOn[i].Location) )
    			bSuccess = true;
    	}
    	return bSuccess;
    }
    This will maintain the registered pawns' list and decide if the mine should detonate.
    This fixes the mines being detonated through walls without harm.
    Does not iterate if we don't have registered pawns.

    Code:
    function TriggerSize()
    {
    	MyTrigger.SetCollisionSize( 75, 75);
    }
    Makes different kinds mines have different trigger sizes

    Code:
    event Destroyed()
    {
    	if ( MyTrigger != none )
    		MyTrigger.Destroy();
    	Super.Destroyed();
    }
    Deletes trigger in actor destruction event.

    ====
    Now we replace the following functions:

    Timer:
    Code:
    simulated event Timer()
    {
    	local Pawn p;
    	Super.Timer();
    	
    
    	if ( SCount > 0 || (Role != ROLE_Authority) )
    		return;
    
    	if ( ShouldDetonate() && !bDisabledByEMP )
    		Damage();
    }
    Even if the Mine is disabled by EMP, ShouldDetonate will run and keep the list in proper order.

    ShouldAttack:
    Code:
    function bool ShouldAttack(Pawn enemy)
    {
    	if ( ScriptedPawn(enemy) != None )
    		return true;
    	if ( sgBuilding(enemy) != None )
    	{
    		if ( sgBuilding(enemy).Team == Team || sgBuilding(enemy).Energy < 0 )
    			return false;
    	}
    	else if ( enemy.PlayerReplicationInfo == None ||
    			enemy.PlayerReplicationInfo.Team == Team ||
    			!enemy.bProjTarget )
    		return false;
    	return true;
    }
    Same as previous Mine post's ShouldAttack, no trace check due to VisibleCollidingActors usage.

    Damage:
    Code:
    function Damage()
    {
    	local int i, j, iP;
    	local Pawn p, pAward, pList[16];
    
    	PlaySound(Sound'SharpExplosion',, 4.0);
    	pAward = GetAwardedPlayer();
    
    	//Hack fix, This iterator doesn't include CollisionRadius, use 25 extra for max detection (for monster support)!!
    	ForEach VisibleCollidingActors (class'Pawn', p, 75+(int(Grade)*25)+25,,true) 
    	{
    		if ( (p.Health > 0) && ShouldAttack(P) )
    		{
    			pList[iP++] = p;
    			if ( iP >= arraycount(pList) )
    				break;
    		}
    	}
    
    	i=Grade;	
    	While ( i-- >= 0 )
    		Spawn (class'MineExplosion',,, Location + VRand() * 40);
    
    	While ( j < iP )
    	{
    		i = 0;
    		While ( VSize( Location - pList[j].Location ) > 75+17+(i*25) )
    			i++;
    		if ( i > Grade )
    		{
    			j++;
    			continue;
    		}
    		i = 1 + int(Grade) - i;
    
    		pList[j].TakeDamage( (Grade+1) * i * 15, instigator, normal( Location - pList[j].Location) * 0.5 * (pList[j].CollisionHeight + pList[j].CollisionRadius), vect(0,0,0), 'mine');
    		sgPRI(pAward.PlayerReplicationInfo).AddRU( (Grade+1) * i * 4);
    		j++;
    	}
    	sgPRI(pAward.PlayerReplicationInfo).AddRU(50);
    
    	Destroy();
    }
    Total rewrite, same effect.
    Unlike RadiusActors, VisibleCollidingActors didn't apply the player's radius into the distance check, fixed here.
    The mine is supposed to detonate in an incremental fashion, depending on the upgrade Level, one per Level and with bigger radius each time.
    We first locate ALL eligible pawns to be hit by an explosion, within the radius of the maximun level explosion (last one in old code), we add it into a user-built array.
    Then we spawn the explosion effects.
    Finally we search the user-built array and find out how many explosions (incremental range) would hit this pawn, if within minimum range (75+17) we get *Level + 1* amount of explosions for example which ends up being variable (i).
    Given how this new iterator is built, instead of calling TakeDamage and AddRu (i) times, we can multiply those values by (i) and call them once instead.


    FinishBuilding:
    Code:
    simulated function FinishBuilding()
    {
        local int i;
        local sgMeshFX newFX;
        local vector spawnLocation;
    	
    	Texture=Texture'Botpack.FLAKAMMOLED';
    
    	if ( Role == ROLE_Authority )
    	{
    		Spawn(class'sgFlash');
    		MyTrigger = Spawn(class'sgMineTrigger', none, 'sgminetrigger');
    		MyTrigger.Master = self;
    		TriggerSize();
    		MyTrigger.SetLocation( Location - vect(0,0,1) );
    	}
    
    	if ( Level.NetMode == NM_DedicatedServer )
    		return;
    
    	spawnLocation = Location;
    	spawnLocation.Z -= 10;
    	if ( myFX == None && Model != None )
    		for ( i = 0; i < numOfMFX; i++ )
    		{
    			newFX = Spawn(class'WildcardsMeshFX', Self,,,
    				rotator(vect(0,0,0)));
    			newFX.NextFX = myFX;
    			myFX = newFX;
    			myFX.Mesh = Model;
    			myFX.DrawScale = DSofMFX;
    			myFX.RotationRate.Pitch = MFXrotX.Pitch*FRand();
    			myFX.RotationRate.Roll = MFXrotX.Roll*FRand();
    			myFX.RotationRate.Yaw = MFXrotX.Yaw*FRand();
    			myFX.AmbientGlow=1;
    			myFX.Style = STY_Translucent;
    			myFX.ScaleGlow = 0.2;
    			//myFX.LODBias=0.200000;
    		}
    }
    We spawn the trigger and link it to this mine.
    The move function is to force Touch on actors that already occupy that spot before spawning the trigger.
    We also call TriggerSize() here.


    Q: Why would a pawn NOT receive all the explosions if when it detonates the mine, is supposed to be at full explosion range?
    A: The pawn that detonates the mine will always receive full damage. It's the pawn that's nearby to the detonator that will receive less damage.

    Q: What's the full damage a mine does?
    A: At level 5, the detonator will receive 540 damage.

    Supermine comes next, don't post yet.

  3. #3
    Whicked Sick Higor's Avatar
    Join Date
    Apr 2012
    Location
    Full sail ahead!
    Posts
    3,676
    Country:
    Now the SuperMine variant, take into consideration that SuperMine is a child class of Mine.


    First, remove this function, it is unneeded now:
    Code:
    simulated event Timer()
    {
        	local Pawn p;
    	Super.Timer();
    
    	if ( SCount > 0 || Role != ROLE_Authority )
            	return;
    
    		foreach RadiusActors(class'Pawn', p, 100)
    			if ( p.bIsPlayer && p.Health > 0 &&
            			p.PlayerReplicationInfo != None &&
              			p.PlayerReplicationInfo.Team != Team && !p.PlayerReplicationInfo.bIsSpectator)
    				if (p != None && !bDisabledByEMP) 
    					Damage();
    }
    This code is shared between both mines, and prevents a double detonation bug when the supermine is placed inside a teleporter causing it to deal 2X damage.


    Code:
    function TriggerSize()
    {
    	MyTrigger.SetCollisionSize( 100, 100);
    }
    Sets size of supermine trigger variant.


    Code:
    function bool ShouldDetonate()
    {
    	local int i;
    	local bool bSuccess;
    
    	For ( i=iCheckOn-1 ; i>=0 ; i-- )
    	{
    		if ( !MyTrigger.IsTouching( CheckOn[i]) ) //Clean up the whole list before returning
    		{
    			CheckOn[i] = CheckOn[--iCheckOn];
    			CheckOn[iCheckOn] = none;
    			continue;
    		}
    
    		if ( !bSuccess && (VSize( CheckOn[i].Location - Location) < (100 + CheckOn[i].CollisionRadius)) && FastTrace(CheckOn[i].Location) )
    			bSuccess = true;
    	}
    	return bSuccess;
    }
    Now uses supermine parameters.


    Code:
    function Damage()
    {
    	local int i, j, iP;
    	local Pawn p, pAward, pList[16];
    
    	PlaySound(Sound'SharpExplosion',, 4.0);
    	pAward = GetAwardedPlayer();
    
    	//Hack fix, This iterator doesn't include CollisionRadius, use 25 extra for max detection (for monster support)!!
    	ForEach VisibleCollidingActors (class'Pawn', p, 100+(int(Grade)*35)+25,,true) 
    	{
    		if ( (p.Health > 0) && ShouldAttack(P) )
    		{
    			pList[iP++] = p;
    			if ( iP >= arraycount(pList) )
    				break;
    		}
    	}
    
    	i=Grade;	
    	While ( i-- >= 0 )
    		Spawn (class'MineExplosion',,, Location + VRand() * 40);
    
    	While ( j < iP )
    	{
    		i = 0;
    		While ( VSize( Location - pList[j].Location ) > 100+17+(i*35) )
    			i++;
    		if ( i > Grade )
    		{
    			j++;
    			continue;
    		}
    		i = 1 + int(Grade) - i;
    
    		pList[j].TakeDamage( (Grade+1) * i * 15, instigator, normal( Location - pList[j].Location) * 0.5 * (pList[j].CollisionHeight + pList[j].CollisionRadius), vect(0,0,0), 'mine');
    		sgPRI(pAward.PlayerReplicationInfo).AddRU( (Grade+1) * i * 4);
    		j++;
    	}
    	sgPRI(pAward.PlayerReplicationInfo).AddRU(50);
    
    	Energy -= 200;
    	if ( Energy <= 0 )
    		Destruct();
    }
    Ok, that's it for now.
    Check for typos or problems as I really can't compile this code.

  4. #4
    Whicked Sick Higor's Avatar
    Join Date
    Apr 2012
    Location
    Full sail ahead!
    Posts
    3,676
    Country:
    Discussion point, there's one thing that's will cause unexpected behaviour here:

    Trigger registers players when they touch it, as long as their health is greater than 0.
    If a player respawns, it is moved to the PlayerStart location before it's health is set to 100, which may cause that a mine doesn't kill a just spawned player.

    Would need some testing...

  5. #5
    Moderator |uK|kenneth's Avatar
    Join Date
    Jan 2011
    Posts
    3,616
    Country:
    Quote Originally Posted by Higor View Post
    Code:
    function TriggerSize()
    {
    	MyTrigger.SetCollisionSize( 100, 100);
    }
    because it does damage on the player who uses the tele, and damage on the tele.

  6. #6
    Whicked Sick Higor's Avatar
    Join Date
    Apr 2012
    Location
    Full sail ahead!
    Posts
    3,676
    Country:
    About the double explosion on tele, it's definetly a bug, and not intended.

    The player finding function is called twice, just like the Damage() one, that is due to the supermine's original Timer calling Super.Timer(), thing is, the supermine's Super.Timer is the mine's Timer().
    I think the intended approach was to call the Timer() from the sgBuilding, an old fix would have been to replace Super.Timer() with Super(sgBuilding).Timer().

    But recoded as it is, there is no need to call another Timer in supermine so we delete it and use the Mine's version.


    EDIT: Why only teles? Because the Mine's timer checks a reduced radius, and only teleporting inside the smaller radius will trigger the double explosion, so yeah, this means old code is calling RadiusActors() twice! per timer.
    Last edited by Higor; 05-26-2012 at 02:52 PM.

  7. #7
    Moderator .seVered.]['s Avatar
    Join Date
    Jun 2011
    Location
    Near a River and Under a Bridge
    Posts
    2,125
    Country:
    About Mines... We seem to have "lost" the post on super mines so I'll post in this thread.

    Has anyone ever SEEN a mine explode with a person place or thing near it? It is NOT PRETTY. A player should NOT be able to stand next to a mine and leech it for the RU...

    If it get's shot, IT EXPLODES!
    PERIOD. or at least a FEW SHOTS but STILL EXPLODES!

    The blast radius should be increased to do more damage to anyone standing close to it when it does explode.
    Last edited by .seVered.][; 07-19-2012 at 04:01 AM.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Similar Threads

  1. Code review: Jetpack
    By Higor in forum #siegepug Discussion
    Replies: 7
    Last Post: 06-01-2012, 04:19 PM
  2. Code review: sgHUD
    By Higor in forum #siegepug Discussion
    Replies: 6
    Last Post: 06-01-2012, 02:45 PM
  3. Code review: Mines 2
    By Higor in forum #siegepug Discussion
    Replies: 5
    Last Post: 05-26-2012, 02:49 PM
  4. Code review: Mines
    By Higor in forum Code Reviews
    Replies: 20
    Last Post: 05-21-2012, 09:36 PM
  5. Code review: Mines
    By Higor in forum #siegepug Discussion
    Replies: 20
    Last Post: 05-21-2012, 09:36 PM

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •