Useful helper functions

Discussions about Coding and Scripting
Post Reply
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Useful helper functions

Post by Buggie »

1. Get screen X Y coordinates for specified Location:

Code: Select all

simulated function float getXY(Canvas C, vector location, out int screenX, out int screenY) {
	local vector X, Y, Z, CamLoc, TargetDir, Dir, XY;
	local rotator CamRot;
	local Actor Camera;
	local float TanFOVx, TanFOVy;
	local float ret;

	C.ViewPort.Actor.PlayerCalcView(Camera, CamLoc, CamRot);

	TanFOVx = Tan(C.ViewPort.Actor.FOVAngle / 114.591559); // 360/Pi = 114.5915590...
	TanFOVy = (C.ClipY / C.ClipX) * TanFOVx;
	GetAxes(CamRot, X, Y, Z);

	TargetDir = Location - CamLoc;

	ret = X dot TargetDir;

	if (ret > 0) {
		Dir = X * (X dot TargetDir);
		XY = TargetDir - Dir;

		screenX = C.ClipX * 0.5 * (1.0 + (XY dot Y) / (VSize(Dir) * TanFOVx));
		screenY = C.ClipY * 0.5 * (1.0 - (XY dot Z) / (VSize(Dir) * TanFOVy));
	}

	return ret;
}
if return value (which is projection of vector from camera to desired point to line of view) less or equal zero, then point behind camera and coordinates can not set.

2. Draw text with outline around it:

Code: Select all

simulated function DrawTextClipped(Canvas C, int X, int Y, string text, Color outline) {
	local Color old;

	old = C.DrawColor;
	C.DrawColor = outline;

	C.SetPos(X - 1, Y - 1);
	C.DrawTextClipped(text, False);
	C.SetPos(X + 1, Y + 1);
	C.DrawTextClipped(text, False);
	C.SetPos(X - 1, Y + 1);
	C.DrawTextClipped(text, False);
	C.SetPos(X + 1, Y - 1);
	C.DrawTextClipped(text, False);

	C.DrawColor = old;
	C.SetPos(X, Y);
	C.DrawTextClipped(text, False);
}
3. Draw line between two points on screen with one pixel texture:
Slow! Don't use often. Better use another approach.

Code: Select all

simulated function DrawLine(Canvas Canvas, int x1, int y1, int x2, int y2) {
	local int i, j, n, dx, dy;
	local float a, b;
	if (x1 == 0 && y1 == 0) return;
	if (x2 == 0 && y2 == 0) return;
	dx = x2 - x1;
	dy = y2 - y1;
	if (Abs(dx) > Abs(dy)) {		
		if (dx == 0) return;
		if (dx > 0) j = 1; else j = -1;
		n = Min(Canvas.ClipX, Max(0, x2));
		a = float(dy)/dx;
		b = float(y1) - a*x1;
		for (i = Min(Canvas.ClipX, Max(0, x1)); i != n; i += j) {
//			canvas.SetPos(i, y1 + dy*(i - x1)/dx);
			canvas.SetPos(i, a*i + b);
			canvas.DrawRect(Texture'Botpack.FacePanel0', 1, 1);
		}
	} else {
		if (dy == 0) return;
		if (dy > 0) j = 1; else j = -1;
		n = Min(Canvas.ClipY, Max(0, y2));
		a = float(dx)/dy;
		b = float(x1) - a*y1;
		for (i = Min(Canvas.ClipY, Max(0, y1)); i != n; i += j) {
//			canvas.SetPos(x1 + dx*(i - y1)/dy, i);
			canvas.SetPos(a*i + b, i);
			canvas.DrawRect(Texture'Botpack.FacePanel0', 1, 1);
		}
	}
}
4. Get value from Actor (work with network play):

Code: Select all

simulated function string getVal(Actor actor, string prop) {
	local string ret;

	Root.Console.ViewPort.Actor.MyHUD.Role = actor.Role; // hack for avoid Enum set issues, here always ROLE_Authority before that
	actor.Role = actor.ENetRole.ROLE_Authority;
	ret = actor.GetPropertyText(prop);
	actor.Role = Root.Console.ViewPort.Actor.MyHUD.Role;
	Root.Console.ViewPort.Actor.MyHUD.Role = actor.ENetRole.ROLE_Authority;
	return ret;
}
Last edited by Buggie on Wed May 05, 2021 11:07 pm, edited 4 times in total.
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

5. Replace first occurrence of substring with another substring. Useful for make placeholders in messages.

Code: Select all

static function string Replace(coerce string source, coerce string search, coerce string replace) {
	var int pos;
	
	pos = InStr(source, search);
	if (pos >= 0) {
		source = Left(source, pos) $ replace $ Mid(source, pos + Len(search));
	}
	
	return source;
}
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Useful helper functions

Post by Barbie »

ad 5) It is also available in Engine.PlayerPawn (but replaces ALL occurrences):

Code: Select all

final function ReplaceText(out string Text, string Replace, string With)
Spoiler

Code: Select all

{
	local int i;
	local string Input;

	Input = Text;
	Text = "";
	i = InStr(Input, Replace);
	while(i != -1)
	{
		Text = Text $ Left(Input, i) $ With;
		Input = Mid(Input, i + Len(Replace));
		i = InStr(Input, Replace);
	}
	Text = Text $ Input;
}
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

I didn’t find this function (although I didn’t really look for it - I looked only at the static functions of the object).
In addition, I can say that there is a difference between the first replacement and the replacement of everything.
For placeholders, you don't need to replace all occurrences.
It depends on the placeholders used though. If they are all different, then there is no difference. If they are the same, then there is a difference, but the order should be fixed, which is not good.
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

6. Draw 3D Line properly, even if one point is going to back of camera.

Code: Select all

simulated function DrawLine3D( Canvas C, vector P1, vector P2, float R, float G, float B ) {
	local int x1, y1, x2, y2;
	local float a1, a2;

	a1 = getXY(C, P1, x1, y1);	
	a2 = getXY(C, P2, x2, y2);
	if (a1 <= 0 && a2 <= 0) return;
	if (a1 <= 0) {
		P1 -= P2;
		P1 *= a2/(a2 - a1);
		P1 -= Normal(P1);
		P1 += P2;
		getXY(C, P1, x1, y1);
	} else if (a2 <= 0) {
		P2 -= P1;
		P2 *= a1/(a1 - a2);
		P2 -= Normal(P2);
		P2 += P1;
		getXY(C, P2, x2, y2);
	}
	if (R >= 0)
		SetColor(C, R, G, B);
	DrawLine(C, x1, y1, x2, y2);
}
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

7. UnrealEd select desired actor.

Code: Select all

function SelectActor(Actor A) {
	local Actor tmp;

	LevelInfo.ConsoleCommand("SELECT NONE");
	ForEach LevelInfo.AllActors(class'Actor', tmp) {
		tmp.bHiddenEd = tmp != A;
	}
	LevelInfo.ConsoleCommand("ACTOR SELECT ALL");
}
8. UnrealEd save and restore bHiddenEd state.

Code: Select all

	local Actor M;
	local string buf;

Code: Select all

	buf = "";
	ForEach LevelInfo.AllActors(class'Actor', M)
		if (M.bHiddenEd)
			buf = buf @ M.Name;
	buf = buf @ "";

Code: Select all

	ForEach LevelInfo.AllActors(class'Actor', M)
		M.bHiddenEd = InStr(buf, " " $ M.Name $ " ") != -1;
User avatar
Feralidragon
Godlike
Posts: 5489
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Useful helper functions

Post by Feralidragon »

Convert HSB (Light values: LightHue, LightSaturation, LightBrightness) to RGB (color):

Code: Select all

final static function color hsbToColor(byte hue, byte saturation, byte brightness)
{
	//local
	local float h, s, b, i, f, p, q, t, cR, cG, cB;
	local color color;
	
	//hsb
	h = float(hue) / 255.0;
	s = 1.0 - float(saturation) / 255.0;
	b = float(brightness) / 255.0;
	
	//calculate
	i = float(int(h * 6.0));
	f = h * 6.0 - i;
	p = b * (1.0 - s);
	q = b * (1.0 - f * s);
	t = b * (1.0 - (1.0 - f) * s);
	
	//set
	switch (int(i) % 6) {
		case 0:
			cR = b;
			cG = t;
			cB = p;
			break;
		case 1:
			cR = q;
			cG = b;
			cB = p;
			break;
		case 2:
			cR = p;
			cG = b;
			cB = t;
			break;
		case 3:
			cR = p;
			cG = q;
			cB = b;
			break;
		case 4:
			cR = t;
			cG = p;
			cB = b;
			break;
		case 5:
			cR = b;
			cG = p;
			cB = q;
			break;
	}
	
	//finalize
	color.R = byte(cR * 255.0);
	color.G = byte(cG * 255.0);
	color.B = byte(cB * 255.0);
	
	//return
	return color;
}
I also have more functions that can be checked at:
https://github.com/Feralidragon/C21FX/b ... s/C21FX.uc

The only reason I don't post them up like the one above, is that most of them are dependent on new structs and enums I have created, so you need to check their definition to understand what the function is actually doing.
Also, don't mind the rest of the repo, it's still all WIP (it's the FX package I am currently working on, so a lot of the code is subject to change).
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Useful helper functions

Post by sektor2111 »

Here I'll show you a sort of actor that can be used in MonsterHunt maps for fixing RazoJack problem and Skaarj. The thing is that this version of actor (I had others before) can be compiled and aiming MonsterHunt issue WITHOUT to have MonsterHunt.u file as package in dependencies chain because the code is set different for preventing creation of links at MonsterHunt which won't compile. It can be embedded in a separate package that can be imported as MyLevel and recompiling it won't be necessary.

Code: Select all

class SomeActor expands Actor;

var class<Weapon> aClass;
var string aClassName;

event PreBeginPlay()
{
}

event PostBeginPlay()
{
	local Ammo Am;

	foreach AllActors(class'Ammo',Am)
	{
		if ( Am.PickupMessageClass == None )
			Am.PickupMessageClass = class 'Botpack.PickupMessagePlus';
	}
}

function FixMH503()
{
	local InventorySpot I;

	if ( aClassName == "" )
	{
		aClassName = "MonsterHunt.OLrazorjack"; //Using string
		aClass = class<Weapon>(DynamicLoadObject(aClassName, class'Class')); //And dynamic assignment if class it's available
	}

	if ( aClass != None )
	foreach AllActors(class'InventorySpot',I)
	{
		if ( I.MarkedItem == None && aClass != None )
		{
			I.MarkedItem = I.Spawn(aClass,,,I.Location+vect(0,0,-10));
			if ( I.MarkedItem != None && I.MarkedItem.MyMarker == None );
				I.MarkedItem.MyMarker = I;
		}
	}
}

function BoostWeaponLoad()
{
	local Weapon W;

	foreach AllActors(class'Weapon',W)
	{
		if ( W.MyMarker != None )
		{
			W.Default.PickupAmmoCount = 9999;
			W.PickupAmmoCount = 9999;
		}
	}
}

function CheckWeapons()
{
	local Weapon W,W1,W2;
	local ScriptedPawn S;

	foreach AllActors (class 'Weapon', W)
	{
		if ( W.MyMarker == None && W.RespawnTime == 0 && W.bRotatingPickup )
		foreach RadiusActors (class 'ScriptedPawn',S,W.CollisionRadius,W.Location)
		if ( SkaarjTrooper(S) != None && S.Health > 0 && S.Weapon == None )
		{
			W.Instigator = S;
			W.SetOwner(S);
			W.bHeldItem=True;
			W.BecomeItem(); //Do not mess anims
			S.AddInventory(W);
			W.GiveAmmo(S);
			W.WeaponSet(S);
			S.bIsPlayer = False;
			break;
		}
	}
}

function CheckPawns()
{
	local Pawn P;
	local ScriptedPawn S;
	local Weapon W2;

	for ( P=Level.PawnList;P!=None;P=P.NextPawn )
	{
		if (!P.bUnlit)
			P.bUnlit = True;
		if ( P.Weapon != None && !P.Weapon.bUnlit )
			P.Weapon.bUnLit = True;
		S = ScriptedPawn(P);
		if ( S != None && S.Health > 0 )
		{
			if ( S.IsA('Nali') || S.IsA('Cow') )
				continue;
			if ( S.bIsPlayer ) //No dependency code below - this is bIsPlayer because it was a null replacement screwing pawn
			{
				if ( aClass != None )
					W2 = S.Spawn(aClass,S,,S.Location,S.Rotation );
				if ( W2 != None )
					W2.RespawnTime = 0.00;
			}

			if ( S.Enemy != None && S.Enemy.IsA('ScriptedPawn') )
			{
				S.Hated = None;
				S.Enemy = None;
				S.bHunting = False;
				if (S.OldEnemy != None && S.OldEnemy.IsA('ScriptedPawn'))
					S.OldEnemy = None;
				if ( S.Orders != '' )
					S.GoToState(S.Orders);
				else
					S.GotoState('Waiting');
			}
			S.Team = 3;
		}
		if ( S != None && S.Health == 1 && S.bHidden ) //Stupid TriggeredDeath & Pawn & PreventDeath craps
			S.Destroy();
	}
}

Auto State AITweaking
{
Begin:
	Sleep(1.50);
	FixMH503();
	Sleep(0.0);
	BoostWeaponLoad();
	Sleep(0.0);
LoopWeapons:
	if ((Level.Game).bGameEnded) GoTo('Goodbye');
	CheckWeapons();
	Sleep(0.15);
	CheckPawns();
	Sleep(0.15);
	GoTo('LoopWeapons'); //Cycling for any future appearance ruined by poorly written game controllers
Goodbye:
	Destroy();
}
Yes, it's tested already.
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

9. Convert name to int index (thanks to Anth):

Code: Select all

class NameObject extends Object;
var Name N;

class IntObject extends Object;
var int I;

class MyBrushBuilder expands BrushBuilder;
var NameObject NO, NO2;
var IntObject IO;
var int NOI;
function int NameToInt(name InName) {
	local string str;
    if (NOI == 0) {
		IO = new(None) class'IntObject';
		NO = new(None) class'NameObject';
		NO2 = new(None) class'NameObject';
	}
    IO.I = 0;
	if (NOI == 2) {
		NOI = 1;
	    NO.N = InName;
	    str = "NO";
	} else {
		NOI = 2;
	    NO2.N = InName;
	    str = "NO2";
	}
    SetPropertyText("IO", GetPropertyText(str));
    return IO.I;
}

event bool Build() {
	local name NN;
	
	NN = 'Botpack'; log(NN @ "=" @ NameToInt(NN));
	NN = 'Enforcer'; log(NN @ "=" @ NameToInt(NN));
	NN = 'Pawn'; log(NN @ "=" @ NameToInt(NN));
	NN = 'Projectile'; log(NN @ "=" @ NameToInt(NN));
}

Code: Select all

ScriptLog: Botpack = 13192
ScriptLog: enforcer = 5345
ScriptLog: Pawn = 1346
ScriptLog: Projectile = 1467
User avatar
Feralidragon
Godlike
Posts: 5489
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Useful helper functions

Post by Feralidragon »

Hmm, that's a very interesting function, so I did some investigation of my own and it seems that works because IntObject is entirely overridden with the contents of NameObject (including properties like the Name property itself, which becomes NameObject0 or NameObject1 in both), and since they have both exactly the same properties in the same order, and since the name type uses 32bits just like the int type, it ends up working like a union (write a name, get back an int).

So that got me exploring a bit, and I was able to simplify the same approach a bit to this:

Code: Select all

//Name store object class
class NameStore extends Object;

var name Value;


//Int store object class
class IntStore extends Object;

var int Value;


//Test class and function
class Test extends Actor;

var NameStore NameStore;
var IntStore IntStore;

function int nameToInt(name name)
{
	//name store
	if (NameStore == none) {
		NameStore = new class'NameStore';
	}
	NameStore.Value = name;
	
	//int store
	if (IntStore == none) {
		IntStore = new class'IntStore';
	}
	
	//set
	setPropertyText("IntStore", getPropertyText("NameStore"));
	
	//return
	return IntStore.Value;
}

But, after realizing how this worked, things got really interesting, and after some testing I figured a way to do a direct cast from Name to Int.
The way I managed to do it requires 2 packages with the same name: one to compile against, and another to use in-game.

1) Create a new package, let's say MyPackage with the following class:

Code: Select all

class Caster extends Object;

function int nameToInt(name value)
{
	
}
leave the function code empty, compile and save this package.
This will be the package you will compile your own package against.

2) Create another package, with the same name as the one above, but this time like this:

Code: Select all

class Caster extends Object;

function int nameToInt(int value)
{
	return value;
}
compile it and save it elsewhere.
This will be the package that you will use and distribute your package with, and is the one that is going to do the actual casting.


In other words, the first package is meant to be used as a library or header to compile with, hence why it has almost no code and has the right function signature.
While the second package is the one that is going to do the actual casting by receiving the name directly as an int, and outputting it directly as an int again.

I have tested this and it works flawlessly thus far.

While this way of casting is more troublesome concerning the setup of the packages, in terms of actual runtime is actually much much faster (it's as direct as it can be) and a lot safer than using objects (especially in that way).
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

Feralidragon wrote: Thu Oct 07, 2021 10:08 pm So that got me exploring a bit, and I was able to simplify the same approach a bit to this:
I start with exact this code. But this produce UnrealEd crash.
1. Check for None not work. Possible only bug of UnrealEd, but look like fields filled with garbage so pass check for None and crash on attempt fill "object" with data.
2. If you try set again to copied object copy again nothing happen. IDK possible some checks fails. So I add two cycling object for copy from it.

Anyway, there can be some improvement, but just cache string representation of two objects. I think it is just name of object. It can be little faster, from convert it each time.
But mostly difference in the performance must be negligible.
Feralidragon wrote: Thu Oct 07, 2021 10:08 pm But, after realizing how this worked, things got really interesting, and after some testing I figured a way to do a direct cast from Name to Int.
The way I managed to do it requires 2 packages with the same name: one to compile against, and another to use in-game.
I think can be little improved, make this stuff less painfull.
You use for compile base class which use function accept name.
But in runtime it replaced with object of subclass where overridden function accept int.
For compile this subclass you change base class and save it in different package.
Later load dynamically and use it in base class.
Now you not need miss with swap packages.
You compile fine, but when time to run you use this hackish small subclass crafted manually.
Buggie
Godlike
Posts: 2698
Joined: Sat Mar 21, 2020 5:32 am

Re: Useful helper functions

Post by Buggie »

10. Dump names table.

Code: Select all

var CodeTrigger IntActor;
var FatnessTrigger NameActor;

exec function DumpIndexes(int start, int end) {
	local PlayerPawn me;
	local int i, way, first, last;

	me = Root.Console.ViewPort.Actor;

	IntActor = me.Spawn(class'CodeTrigger');
	SetPropertyText("NameActor", GetPropertyText("IntActor"));
	
	NameActor.FatTag = IntActor.Name;
	last = Max(last, IntActor.Code);
	
	Log("Start dump from" @ start @ "to" @ end @ "from interval" @ first @ "-" @ last);
	start = min(max(first, start), last);
	end = min(last + 1, max(end, first + 1));
	
	if (start < end) way = 1;
	else way = -1;

	for (i = start; i != end; i += way) {
		IntActor.Code = i;
		Log("Name[" $ i $ "] = '" $ NameActor.FatTag $ "'");
	}
	
	IntActor.Destroy();
}
Some names cause UT crash. usually located below index 1200 and vary for UT version.
469 crash on 1199 and 841 for example.
436 crash on 819.
Both crash on 16.
Post Reply