Invisible placeables?

Is there any way to make a placeable invisible in an area? I have a placeable object, an altar, that I thought of spawning in into an area if a certain thing happens. The problem is, this altar is to have a symbol on top of it, and that means I have to spawn that one in too, and that is really problematic if one is to match height and all that to make it look ok. Therefore I wondered if I could just paint the placeable and the symbol on top of it in the area, and make them both invisible and then visible if certain conditions is fulfilled.

Hi andgalf,

Yes, I believe I have done something in my own module, The Scroll. This falls under my “placeable trickery” ideas I mentioned in one of my blog posts. :wink:

You have to place the objects in the module at build time and then have their heartbeat script set their scale to 0.001, 0.001, 0.001 ready to be rescaled to 1,1,1 (or whatever) when required. Note, if you wish to change the default scale prior to any scaling, then you can do that too. You cannot make it 0.0,0.0,0.0 as that can break code as I discovered in one of my own bug reports.

You also then make the really tiny placeable Not Useable by the appropriate function so it is ignored.

Below is a copy of my own script (alb_make_glow) that does the process (with some other stuff too). You need to write your specific script to set scale to normal again. The script is a little “scatty” as it has undergone various changes over time and could do with some rework. :wink:

EDIT: Default usage is that you place a value of 1 in “SHRINK” on placeable already placed into position and it will be “invisible” when you enter the game.

Check out my module for more specific examples.

Cheers, Lance.

// MAKE A PLACEABLE OBJECT GLOW (DEFAULTS TO BLUE BLUR) 

// FOUND A FUNCTION CALLED AddHighlight (SEE x0_i0_highlight), which mentions these VFXs:
//   VFX_DUR_ETHEREAL_VISAGE: glowing, purple, transparent
//   VFX_DUR_GHOSTLY_VISAGE: glowing, blue, transparent
//   VFX_DUR_BLUR: alternating blue/white pulsing glow
//   VFX_DUR_PARALYZED: alternating red/white pulsing glow		= 232
//   VFX_DUR_PROT_SHADOW_ARMOR: shimmery opaque black glow		= 14
//   VFX_DUR_MIND_AFFECTING_DOMINATED: spinning crown of lights
//   VFX_DUR_MIND_AFFECTING_DISABLED: crown of sparkling lights
// There are also one or two other interesting functions in that include.

void main()
{		
	// PREPARE SHRUNK FOR REAPPEAR BLOCK (3 IS RECREATE)
	if(GetLocalInt(OBJECT_SELF, "SHRINK") == 3)
	{
		object oMod = GetModule();
		location lPlace = GetLocation(OBJECT_SELF);
		// SET A UNIQUE IDENTIFIER USED IN A DEDICATED SCRIPT
		SetLocalLocation(oMod, GetName(OBJECT_SELF) + "_LOCATION", lPlace);
		DestroyObject(OBJECT_SELF, 0.1, FALSE);	
		return;	
	}
	
	// PREPARE SHRUNK FOR REAPPEAR VISUAL (2 MEANS NO GLOW ADDED)
	if(GetLocalInt(OBJECT_SELF, "SHRINK") == 2)
	{
		SetUseableFlag(OBJECT_SELF, FALSE);
		DeleteLocalInt(OBJECT_SELF, "SHRINK");
		SetScale(OBJECT_SELF, 0.001,0.001,0.001);
		SetLocalInt(OBJECT_SELF, "VFXDONE", 1);	
		return;	
	}
	
	object oAreaPC = GetArea(GetFirstPC());			// ALLOW DO TO START
	if(GetArea(OBJECT_SELF) != oAreaPC){return;}
		
	// EFFECT HAS BEEN APPLIED
	if(GetLocalInt(OBJECT_SELF, "VFXDONE") == 1){return;}
	SetLocalInt(OBJECT_SELF, "VFXDONE", 1);	
	
	// APPLY A VISUAL EFFECT TO THIS OBJECT
	int iVFX = GetLocalInt(OBJECT_SELF, "VFX"); // REFER TO VISUAL EFFECTS CHARTS FOR INTEGER CONSTANTS			
	string sSefVFX = GetLocalString(OBJECT_SELF, "SEFVFX"); // ALTERNATIVE SEF TO APPLY				
	
	// DEFAULT EFFECT TO APPLY
	if(iVFX == 0 && sSefVFX == ""){iVFX = VFX_DUR_BLUR;}
		
	//SendMessageToPC(GetFirstPC(FALSE), "VFX >>> " + IntToString(iVFX) + " SEF >>> " + sSefVFX);
	
	effect eGlow = EffectVisualEffect(iVFX);
	if(sSefVFX != ""){eGlow = EffectNWN2SpecialEffectFile(sSefVFX);}		
	eGlow = SupernaturalEffect (eGlow);
	
	ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGlow, OBJECT_SELF);	
	
	// PREPARE SHRUNK FOR REAPPEAR VISUAL
	if(GetLocalInt(OBJECT_SELF, "SHRINK") == 1)
	{
		SetUseableFlag(OBJECT_SELF, FALSE);
		DeleteLocalInt(OBJECT_SELF, "SHRINK");
		SetScale(OBJECT_SELF, 0.001,0.001,0.001);		
	}
	
}

And here is a CONVERSATION version … (alb_conv_scale)

// SET THE BASE SCALE OF AN OBJECT TO MAKE "DISAPPEAR" AND "REAPPEAR" IN A CONVERSATION
// USEFUL TO MAKE MAP PLACEABLES NOT APPEAR IN CUTSCENE WHEN "CONVERSING" WITH THEM
// DEFAULT VISIBILITY IS 0 (ZERO) WHICH MAKES THE OBJECT "DISAPPEAR"
// MAKE VISIBLE 1 TO MAKE IT REAPPEAR (RESETS SCALE FACTOR TO ORIGINAL SIZE OF 1.0)
// ALTER ISCALEGROWTHFACTOR TO ALTER RESCALE GROWTH (DEFAULT IS A FACTOR OF 1 TO ORIGINAL)
// DEFAULT OBJECT IS CONV OWNER UNLESS STAGSCALEOBJ REFERS TO ANOTHER
// SET AllTagObjects TO 1 FOR ALL TAGGED OBJECTS IN MODULE (OVERRIDES CONV OWNER)

// !!!! NOTE: SCALING SECTION HAS NOT BEEN TESTED YET !!!!

void DoVisibilityScaling(int Visible, object oScale, float fScaleGrowthFactorX = 1.0, float fScaleGrowthFactorY = 1.0, float fScaleGrowthFactorZ = 1.0, int iMAKEUSE = -1)
{
	// JUST MAKING "INVISIBLE"
	if(Visible == 0)
	{
		SetUseableFlag(oScale, FALSE);
		DelayCommand(0.1, SetScale(oScale, 0.001, 0.001, 0.001)); 
	}	
	
	else
	{		
		// MAKING VISIBLE (WITH SCALING?)	
		if(iMAKEUSE == -1)
		{		
			DelayCommand(1.0, SetScale(oScale, fScaleGrowthFactorX, fScaleGrowthFactorY, fScaleGrowthFactorZ));
		}
		
		// SET USEABLE DOES NOT WORK RELIABLY (RECREATE USEABLE ITEMS AND USE THESE ONES AS REF ONLY)
		// PLACEABLE OBJECTS MUST HAVE COPIES IN THE TOOLSET TO CREATE FROM E.g. DemonBowl_4 = demonbowl_4
		else
		{
			string sTAGREF = GetTag(oScale);
			string sLowerRR = GetStringLowerCase(sTAGREF);
			location lHere = GetLocation(oScale);
			object oNEW = CreateObject(OBJECT_TYPE_PLACEABLE, sLowerRR, lHere);
			SetLocalInt(oNEW, "IGNOREUPDATE", 1); // OTHERWISE MAY CAUSE LOOP ISSUES ON ALL TAGS (LAG ETC)
			
			// MUST DESTROY THE OLD VERSION BECAUSE ITS TAG IS USED IN RITUAL CHECKS! ARGHH!!!!
			DestroyObject(oScale, 0.1);
		}
		
		// BRIEF FLASH TO SHOW DIFFERENCE (NO - CAUSES SETUSEABLEFLAG TO FAIL)
		effect eFLASH = EffectNWN2SpecialEffectFile("fx_aoo_hit");
		DelayCommand(0.5, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eFLASH, GetLocation(oScale)));				
	}
}

void main (int Visible = 0, string sTagScaleObj = "", float fScaleGrowthFactorX = 1.0, float fScaleGrowthFactorY = 1.0, float fScaleGrowthFactorZ = 1.0, int AllTagObjects = 0, int iMAKEUSE = 0)
{
	// PREVENT ZERO ENTRIES
	if(fScaleGrowthFactorX == 0.0){fScaleGrowthFactorX = 1.0;}
	if(fScaleGrowthFactorY == 0.0){fScaleGrowthFactorY = 1.0;}
	if(fScaleGrowthFactorZ == 0.0){fScaleGrowthFactorZ = 1.0;}
		
	// OWNER OE TARGET OBJECT
	object oScale = OBJECT_SELF; int nCount = 0;
	
	if(sTagScaleObj != "")
	{
		oScale = GetObjectByTag(sTagScaleObj, nCount);		
	}
		
	// MULTI OBJECT ALTERATION
	if(AllTagObjects == 1)
	{		
		while(oScale != OBJECT_INVALID)
		{
			if(GetLocalInt(oScale, "IGNOREUPDATE") == 0)
			{
				DoVisibilityScaling(Visible, oScale, fScaleGrowthFactorX, fScaleGrowthFactorY, fScaleGrowthFactorZ, iMAKEUSE);
			}
			
			nCount = nCount + 1; oScale = GetObjectByTag(sTagScaleObj, nCount);
		}
	}	
	
	else
	{
		DoVisibilityScaling(Visible, oScale, fScaleGrowthFactorX, fScaleGrowthFactorY, fScaleGrowthFactorZ, iMAKEUSE);
	}
}

Another option is to just spawn them in and use a waypoint as the reference to spawn in at. I do this a lot on my PW with “secret” items that you can find hidden on book shelves, crypt wall nooks and other places. What I do is just set and manually adjust the placeable(s) in the toolset first. Then I record their X,Y and Z axis and their facing into a notepad file. I then delete them and drop waypoints that are tagged for what I want and manually adjust them to the recorded position(s).

A lot depends on how you want to end up using the placeable …

My conversation version does something similar to what kalbaern does, often by recreating the placeables from fresh using “original downscaled markers” (like WPs), whereas the first version works on stuff already placed.

I tend to use various methods dependant upon what I need it to do. :slight_smile:

Thanks for the replies! I’ll try your idea out Lance, as I feel that that may be the easiest way to do it. Pretty clever actually.

I’m thinking that maybe I could apply a script, maybe yours, under OnClientEnter of the area, and when the conditions are right it scales up the placeables. It feels a little bit weird to place it under heartbeat since that checks every 6 seconds (or so I’ve been taught, I think). I mean I feel I don’t need to run this script every 6 seconds…

Well your script was pretty advanced, and I’m not that good at scripting, so I made mine a lot simpler. The strange thing is that I can’t get it to work. Don’t know why. I tested by putting this first on OnClientEnter and then on OnEnter but nothing seemed to work. Maybe I’m forgetting something:

void main()
{

if(GetGlobalInt("altarset"))
	{
	return;
	}

object oScale = GetNearestObjectByTag("shrinew");
object oScale2 = GetNearestObjectByTag("signw");
object oScale3 = GetNearestObjectByTag("holyfr");
	
DelayCommand(0.1, SetScale(oScale, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale2, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale3, 0.001, 0.001, 0.001));

}

Hi andgalf,

Try defining the PC so the function knows what it’s trying to find … or use GetObjectByTag instead and that function will scale any uniquely tagged object you defined. i.e. Not the nearest, but any with tag. If so, you would simply keep your script and replace GetNearestObjectByTag with GetObjectByTag.

Also be sure that your altarset is zero - I would even comment it our until tested as working.

Cheers, Lance.

void main()
{

if(GetGlobalInt("altarset"))
	{
	return;
	}

	object oPC = GetEnteringObject();
	
object oScale = GetNearestObjectByTag("shrinew", oPC);
object oScale2 = GetNearestObjectByTag("signw", oPC);
object oScale3 = GetNearestObjectByTag("holyfr", oPC);
	
DelayCommand(0.1, SetScale(oScale, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale2, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale3, 0.001, 0.001, 0.001));

}

or closer to your script the other way …

void main()
{

if(GetGlobalInt("altarset"))
	{
	return;
	}
	
object oScale = GetObjectByTag("shrinew");
object oScale2 = GetObjectByTag("signw");
object oScale3 = GetObjectByTag("holyfr");
	
DelayCommand(0.1, SetScale(oScale, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale2, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale3, 0.001, 0.001, 0.001));

}

I would recommend adding the SetUseableFlag function as you could still see it with careful pixel hunting. :slight_smile:

EDIT: I intend to rework some of my own scripts to be less dependant on HB scripts, so it’s not a bad thing you are working from an OnEnter script.

these worked for me

// 'area_ce'
/*
    OnClientEnter script
*/

const float SMALL = 0.001f;

void main()
{
    if (!GetLocalInt(OBJECT_SELF, "done_Lockbox"))
    {
        object oEnter = GetEnteringObject();
        object oLockbox = GetNearestObjectByTag("lockbox", oEnter);
        if (GetIsObjectValid(oLockbox))
        {
            SetLocalInt(OBJECT_SELF, "done_Lockbox", TRUE);
            SetScale(oLockbox, SMALL, SMALL, SMALL);
        }
    }
}
// 'restore_lockbox'
/*
    Console script
*/

const float NORMAL = 1.f;

void main()
{
    object oLockbox = GetObjectByTag("lockbox");
    if (GetIsObjectValid(oLockbox)
        && !GetLocalInt(oLockbox, "done_Restore"))
    {
        SetLocalInt(oLockbox, "done_Restore", TRUE);
        SetScale(oLockbox, NORMAL, NORMAL, NORMAL);
    }
}

Thanks again for the replies! I didn’t think about using GetEnteringObject on OnClientEnter since I thought it ran anyway in when using OnClientEnter entering an area. In other OnClientEnter scripts from ColorsFade when spawning a companion he didn’t use GetEnteringObject or GetFirstPC, so that’s why I didn’t think it necessary here. Apparently I was wrong. The reason I was using GetNearestObjectByTag is that this is just in this area where you enter, and that’s why I thought I would be a little bit nice to the computer to not have to go through the whole module to get the object. I have another script that runs when the PC is in another area, when I rescale the objects to normal size, and there I use GetObjectByTag.

A wait…rereading your post again, I think I understand now. The function GetNearestObjectByTag needs the PC but not GetObjectByTag, right?

And your scripts kevL_s were pretty neat. A bit more compact. I’ll see what I’ll use.

3 Likes

GetNearestObjectByTag() is likely more efficient than GetObjectByTag() yes.

The problem is that in an Area event script, I don’t think you can use oArea (OBJECT_SELF) as the object to get the nearest object to … that’s why i defined oEnter and get-nearest to oEnter.

In the console script i could have used GetNearest to OBJECT_SELF since OBJECT_SELF is my PC. But instead of fussing about it, just went with GetObjectByTag … so, which function to use depends on what object is going to run the restore-script,

 
( in fact it wouldn’t surprise me if a GetFirst/NextObjectInArea() loop is faster than either of those functions )

3 Likes

Hi andgalf,

DELETED … I confused myself between OnClientEnter and OnAreaEnter, so forget what I had here. :blush:

Basically, as has already been mentioned and you surmised, the additional function ensures the GetNearestObjectByTag function you were using has a “starting point” for the area to start searching.

Also, with respect to heartbeat scripts, I have found that we (and I include myself here), probably get too concerned over their impact than we actually need to. :slight_smile: I say this because I have quite a number now, and I am NOT convinced they make too big an impact, especially if we control when they are used. i.e. Maybe only when a PC is in the area in which the script is placed … or self-terminate. Although, as I said earlier, I still think it is good practice to not use them if we can, and is why I am considering an overhaul on some of my own. :wink:

I think that KevL scripts and the ones I gave as examples are pretty much the same approach, and so I reckon using that approach is reasonably sound. :slight_smile:

Cheers, Lance.

EDIT:

Yes. :slight_smile: Or more specifically, any object reference.

1 Like

Ah, then it makes even more sense.

As it is now, I went with a slight modification of my original script and it works splendidly now:

void main()
{

if(GetGlobalInt("altarset"))
	{
	return;
	}

	
	object oPC = GetEnteringObject();
	
object oScale = GetNearestObjectByTag("shrinew",oPC);
object oScale2 = GetNearestObjectByTag("signw",oPC);
object oScale3 = GetNearestObjectByTag("holyfr",oPC);
	
DelayCommand(0.1, SetScale(oScale, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale2, 0.001, 0.001, 0.001));
DelayCommand(0.1, SetScale(oScale3, 0.001, 0.001, 0.001));

}

I will try out my restore script now (which runs in a different area, or rather, when I leave this area) and there I use GetObjectByTag like this:

		object oScale = GetObjectByTag("shrinew");
		object oScale2 = GetObjectByTag("signw");
		object oScale3 = GetObjectByTag("holyfr");
	
		DelayCommand(0.1, SetScale(oScale, 0.3, 1.0, 1.0));
		DelayCommand(0.1, SetScale(oScale2, 0.1, 0.1, 0.1));
		DelayCommand(0.1, SetScale(oScale3, 1.0, 1.0, 1.0));

		SetGlobalInt("altarset",1);

Maybe you’re right, Lance, that we shouldn’t be afraid to use heartbeat scripts, I just felt it unnecessary for me in this case since I will only use these scripts one time. Well, ok, not quite…I will use the shrink script every time the PC enter the area except when the GlobalInt is set to “altarset”. I felt your script was too advanced/complicated for the small thing I wanted to do (but I will keep it somewhere safe if I ever need to do something like you’ve described in your module, and go back and modify it for my needs, so thank you!)

1 Like

The concole script…I don’t think I know what that is. Does it have to do with PWs and multiplayer, perhaps? I think I know what “bringing up the concole” means…Isn’t that when you try to modify something in game, while in the game…?

yes –

`
debugmode 1
rs my_console_script
debugmode 0
`

but, at least for the “restore” routine, it amounts to the same as placing down, say, a trigger and assigning the script to the trigger’s OnEnter … i just wanted a quick way to restore my lockbox

and, my conclusion re. OnHeartbeat is the same as Lance’s – treat them carefully (sometimes very carefully) and they’re fine on modern computers. When you think about it, processing graphics (eg) is taking orders of magnitude more CPU cycles than any HB script – when helping a little with the development of OpenXcom some years ago I realized just how much a CPU works away at stuff : A LOT.

a heartbeat script is a drop in an ocean

2 Likes