Need help with trace function for decal placement

Discussions about Coding and Scripting
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Need help with trace function for decal placement

Post by JackGriffin »

I get your point but it's different to have it 'snowing' or 'raining' among clients versus painting graffiti on the walls. If players compare them (and I very much hope they do, I found some old ones) it would ruin the effect if they saw different things or some saw none at all.
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Need help with trace function for decal placement

Post by sektor2111 »

Okay, when you'll have them done and if you don't mind to share code, I'll see what I can do. For firsts supposed goals I'm thinking to split tasks in two parts:
- Server replicating random sorted nodes (probably locations only sent to clients) and that's all;
- Client processing traces and spawning decals according to what server said about locations - they should have the same Level I'm guessing, and each of them should know what to do without to fail or having the same failure and the "heavy duty" goes here.

My problem here is that I don't know too much about handling these vectors else I could do some experiments (like before).
This way of negotiation started in server will allow even empty maps with run-time paths to have these decals but still handled by client toward walls - if mutator is being started with a delay, this is doable already as long as I have done a delayer loader tool which seems useful so far.
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Need help with trace function for decal placement

Post by JackGriffin »

OFC I'll share the code (for what that's worth). Credit goes to Ferali and Higor for walking me through trace code.

I'm using four classes:

The first is the mutator. It does all the heavy lifting.

Code: Select all


class GraffitiMod extends Mutator config(BRUT);

var int NumNodes, NavPoint, SprayAttempts;
var bool Initialized, GraffitiAllDone;
var NavigationPoint PNodes[5001];
var config int GraffitiNumber;

replication
{
   reliable if ( Role == ROLE_Authority )
   	GraffitiNumber;
}

function PostBeginPlay()
{
	local NavigationPoint NP;

   if (Initialized)
      return;
   Initialized = True;

   SprayAttempts = 0;

   for (NP = Level.NavigationPointList; NP != None; NP = NP.NextNavigationPoint)
   {
      if (NP.IsA('Pathnode') && !NP.Region.Zone.bWaterZone)
      {
      	if(NumNodes <= 5000)
      	{
      		PNodes[NumNodes] = NP;
         	NumNodes++;
         }
      }
   }

   SetTimer(0.5,True);
}

simulated Function addMutator(mutator M)
{
	if(M.class==self.class)
		m.destroy();
	else super.addMutator(M);
}

function SpawnGraffiti()
{
   local int PointCount;
   local NavigationPoint NP;
   local rotator newRot;
	local vector HitLocation, HitNormal, FireDir, X, Y, Z, Origin, StartTrace, EndTrace;
   local actor HitActor, Other;
   local int i, j, N, M, SpriteX, SpriteY;
   local float yOffset, zOffset;
   local tag1 SpawnedGraffitiRocket;

   NavPoint = int(RandRange(0,NumNodes));
   for (NP = Level.NavigationPointList; NP != None; NP = NP.NextNavigationPoint)
   {
      if (NP.IsA('PathNode'))
      {
         if (PointCount == NavPoint)
         {
				newRot.Yaw = Rand(65535); //360 degrees around
				newRot.Pitch = Rand(1820); //from 0 to roughly 10 degrees upward angle
				newRot.Roll = 0;
				FireDir = vector(newRot);
				HitActor = Trace(HitLocation, HitNormal, location + FireDir * 1500, Location, false);
				if(( HitActor == Level) && ( HitNormal.Z < 0.33 ) && ( HitNormal.Z > -0.33 )) //Allow for some wall lean, about 30 degrees
				{
    				GetAxes( rotator(-HitNormal), X, Y, Z);
   				Origin = HitLocation + HitNormal;

					//Pass 1
					StartTrace = Origin;
					EndTrace =  Origin + Y * 64 + Z * 64;
					Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
					if (Other != none)
					   return;
					else
					{
					   StartTrace = Origin + Y * 64 + Z * 64;
					   EndTrace = StartTrace + X * 3;
					   Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
						if (Other != none)
						{
                     //Pass 2
                     StartTrace = Origin;
							EndTrace =  Origin + Y * -64 + Z * 64;
							Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
							if (Other != none)
							   return;
							else
							{
							   StartTrace = Origin + Y * -64 + Z * 64;
							   EndTrace = StartTrace + X * 3;
							   Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
								if (Other != none)
								{
									//Pass 3
									StartTrace = Origin;
									EndTrace =  Origin + Y * 64 + Z * -64;
									Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
									if (Other != none)
									   return;
									else
									{
									   StartTrace = Origin + Y * 64 + Z * -64;
									   EndTrace = StartTrace + X * 3;
									   Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
										if (Other != none)
										{
											//Pass4
											StartTrace = Origin;
											EndTrace =  Origin + Y * -64 + Z * -64;
											Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
											if (Other != none)
											   return;
											else
											{
											   StartTrace = Origin + Y * -64 + Z * -64;
											   EndTrace = StartTrace + X * 3;
											   Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
												if (Other != none)
        											SpawnedGraffitiRocket = Spawn(class'tag1',,, Origin,rotator(-HitNormal));
         								}
         							}  else return;
         						}
         					} else return;
         				}
         			} else return;
         		}
         	} else return;
  			} PointCount++;
      }
   }
}

simulated function Timer()
{
	local Tagz1 T;
	local int Q;
	local PathNode PN;

	if (Level.Game.bGameEnded) return;

	if(!GraffitiAllDone)
	{
		SprayAttempts +=1;
		if (SprayAttempts > 60) //cap number of tries to prevent runaway
			GraffitiAllDone = true;
  		Q=0;
  		forEach AllActors(class'Tagz1', T) //cap number of sprays for the map
  		{
  			if (T !=none)
        		Q +=1;
      	if (Q >= GraffitiNumber)
  				GraffitiAllDone = true;
  		}

		if(!GraffitiAllDone)
  			SpawnGraffiti();
  		else
  			SetTimer(0, False);
  	}
}

defaultproperties
{
	GraffitiAllDone=False
}
Tag1 is the silent rocket subclass that is spawned right beside the selected spot for the graffiti.

Code: Select all

class tag1 extends RocketMk2;

#exec AUDIO IMPORT FILE="Sounds\silence.wav" 		NAME=silence

var float SmokeRate;
var bool bRing,bHitWater,bWaterStart;
var int NumExtraRockets;
var rockettrail trail;

simulated function Destroyed()
{
   if ( Trail != None )
      Trail.Destroy();
   Super.Destroyed();
}

simulated function PostBeginPlay()
{
   Trail = None;
   SoundRadius = 0;
   SmokeRate = 0;
   LightRadius = 0;
}

simulated function Timer()
{
}

auto state Flying
{
	simulated function HitWall (vector HitNormal, actor Wall)
	{
		local GraffitiLight b, q;
		local bool AreaClear;

    	Explode(Location + ExploWallOut * HitNormal, HitNormal);
    	AreaClear = True;
      //Check the local area for existing sprays, skip if there are any
    	foreach RadiusActors(class 'GraffitiLight', q, 256)
    	{
    	    if( Q != none)
    	       AreaClear = False;

    	}
    	if ((ExplosionDecal != None) && (Level.NetMode != NM_DedicatedServer) && (AreaClear))
    	{
    		if (( HitNormal.Z < 0.33 ) && ( HitNormal.Z > -0.33 ))
    		{
        		Spawn(ExplosionDecal,self,,Location, rotator(HitNormal));
        		b = Spawn(class'GraffitiLight',self,,Location + HitNormal*32);
        		b.remoterole=ROLE_simulatedproxy;
        	}
      }
	}

   simulated function ZoneChange( Zoneinfo NewZone )
   {
   }

   simulated function ProcessTouch (Actor Other, Vector HitLocation)
   {
      if ( (Other != instigator) && !Other.IsA('Projectile') )
         Explode(HitLocation,Normal(HitLocation-Other.Location));
   }

   simulated function Explode(vector HitLocation, vector HitNormal)
   {
      Destroy();
   }

   function BeginState()
   {
      local vector Dir;

      Dir = vector(Rotation);
      Velocity = speed * Dir;
      Acceleration = Dir * 50;
      PlayAnim( 'Wing', 0.2 );
      if (Region.Zone.bWaterZone)
      {
         bHitWater = True;
         Destroy();
      }
   }
}

defaultproperties
{
   speed=100.00
   Damage=0.00
   MomentumTransfer=1
   SpawnSound=Sound'silence'
   ImpactSound=Sound'silence'
   ExplosionDecal=Class'Tagz1'
   LifeSpan=4.0
   AmbientSound=Sound'silence'
   DrawScale=0.00
   AmbientGlow=0
   SoundRadius=0
   SoundVolume=0
   LightType=0
   LightBrightness=0
   LightRadius=0
}
Tagz1 is the blastmark subclass that is the actual graffiti

Code: Select all

class Tagz1 expands BlastMark;

#exec TEXTURE IMPORT NAME=SprayA 	FILE=Textures\Graffiti\SprayA.BMP 	GROUP=Sprays
#exec TEXTURE IMPORT NAME=SprayB 	FILE=Textures\Graffiti\SprayB.BMP 	GROUP=Sprays
#exec TEXTURE IMPORT NAME=SprayC 	FILE=Textures\Graffiti\SprayC.BMP 	GROUP=Sprays
#exec TEXTURE IMPORT NAME=SprayD 	FILE=Textures\Graffiti\SprayD.BMP 	GROUP=Sprays
///etc

var Texture Tag1Tex;

replication
{
   reliable if (Role == ROLE_Authority)
   	Tag1Tex;
}

simulated function PostBeginPlay()
{
 	local Tagz1 T;
 	local int TexChoice;

   Super.PostBeginPlay();
   SetTimer(1.0, false);

   TexChoice = rand(4)+1;
   switch(TexChoice)
   {
   	case 1:
   		self.Texture = texture'SprayA';
   		self.Style = STY_Modulated;
   		break;
   	case 2:
   		self.Texture = texture'SprayB';
   		self.Style = STY_Modulated;
   		break;
   	case 3:
   		self.Texture = texture'SprayC';
   		self.Style = STY_Modulated;
   		break;
   	case 4:
   		self.Texture = texture'SprayD';
    		self.Style = STY_Modulated;
   		break;
   }
}

simulated function DirectionalAttach(vector Dir, vector Norm)
{
   SetRotation(rotator(Norm));
	Log(Norm);
	Log(Dir);
	if( AttachDecal(100, Dir) == None)
	{
		Destroy();
	}
}

simulated function AttachToSurface()
{
	bAttached = AttachDecal(100, vect(0,0,1)) != None;
}

simulated function Timer()
{
}

defaultproperties
{
 	Drawscale=1.0
}
and finally graffitilight is a light subclass that is spawned on the graffiti so you can see it in dark spots but it's also used to check so graffiti don't overlap each other.

Code: Select all

// This spawns a small light to illuminate the sprays and it also
// let's me check for existing sprays so they don't overlap.
class GraffitiLight extends Decoration;

defaultproperties
{
	bUnlit=True
   Physics=PHYS_Flying
   bPushable=False
  	DrawType=DT_Mesh
  	Mesh=Mesh'CandleM'
  	bCollideActors=True
  	bCollideWorld=True
  	bBlockActors=True
  	bBlockPlayers=True
  	CollisionRadius=0.00
  	CollisionHeight=0.00
  	bStatic=False
  	LifeSpan=99999
  	LightType=LT_Steady
  	LightRadius=5
  	LightBrightness=100
  	LightHue=0
  	LightSaturation=255
  	Drawscale=0.00001
}
Now bear in mind that this mod was written with hard checks for all the textures being 128X128. If you want to use other sizes than that you'll need to adjust the tracing. This code will compile, all you need to do is add four textures for it to use. I've been testing this code for a couple of days so if I find improvements I'll update the post. Same thing applies if you see something I've missed.

In practice this works very well. I haven't seen it mess up yet, though it does struggle to find good spots. I have attempts capped at 60 but I'm going to increase that to probably 100 and speed the timer up to 3 times a second. It's running at 2 times and there is no impact at all to the server. A lot of the maps are ending with 60 tries and only one or two graffiti being applied but this is due to the lack of good, flat, and open wall space to use. That's fine, I'd rather it's not applied than done half on a wall.

If you compile this code to release as a mod you are welcome to use whatever name you want. I've included this code into the BRUT mod so there won't be a conflict. Please credit Ferali and Higor if you do, I just assembled their instructions.

-Kelly
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Need help with trace function for decal placement

Post by sektor2111 »

Everyone get credits, even the dude making them straight not rotated randomly (recall our old chat at HOF) I know some of these bits except traces which are newer in here. I did some of them bigger, taller, smaller, various sizes but perhaps for a general mod it will be recommended following an unique size except special situations used by some map on purpose, I can have these bigger (cough to that 34 Big Font topic issue) or grouped for making a single banner from pieces 8) .
Applications which I'm thinking:
- some server advertisements - perhaps more nice this way;
- some stuff for seasons/events (winter, summer, etc. Christmas ) - here goes adds configured and mapped as packages when are defined (XC task here);
- various animated textures - I believe they work even animated, if texture in cause has frames;
- mapping screen-type stuff using multi-framed textures or with a limited LifeSpan decals.

Probably others have different ideas - a map self-decorating at random - not much philosophy here, this is more simple with using hard-coded spots.
For me this it's indeed an interesting stuff. I have to admit that in last patch files I did not do to many paintings but I used them a couple of times... they are helpful in poorly textured maps. I should use some of these in last map which I debated but I've really forgot about them.

/Nasty Mode on
Perhaps I would test every single PathNode if has a suitable position for deploying a decal around capped at 100-200 pieces - there are not many maps friendly here. Some of those darkish textures are not polite with decals. These pictures should take a lot of factors in account.
/Nasty Mode off

Creativity can go more far but I have to admit all this it's a time consuming task.
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Need help with trace function for decal placement

Post by JackGriffin »

I'll tell you that in practice it's actually pretty hard to randomly find a 128X128 flat wall surface that isn't obstructed when you are just blindly shooting out traces. Even in overall cubic maps like most of the stock DM maps you still may not get a single clear spot in 60 tries. I was pretty surprised at that but I can't really think of a way to improve shooting the trace in the first place. If you went to a larger size it's going to be much harder. You might consider making a panel and applying the texture to that then finding a generally decent spot to spawn that.
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Need help with trace function for decal placement

Post by sektor2111 »

Yes, but if I demand certain spot in sorted maps decals/banners might have place - and a lot of place. Sample of these which I've done VIA patch plugins:
- MH-super_tomokoV2 - in week-end you might see some stuff - bigger than 128×128;
- MH-URealV3 or such - also banner having more than 128×128 fitting exactly on a wall closer to start and then a few "wall-crack" with random texture;
- MH-Purgatorium - some evil signs.

I think I will reconsider adding them because I really liked them - I think even a contest badge can be spread like that without using another brush.
But... I'm not sure if tiny things having 64×64 cannot be used.
Post Reply