Push Button Controls with Geometry, Part II

From C4 Engine Wiki

Jump to: navigation, search
Image:warning.jpg Obsolete Article

Some or all of the information on this page is obsolete and needs to be updated.

This article is a continuation of Push Button Controls with Geometry, Part I. If you have not completed Part I of this tutorial, you must do so before proceeding with Part II.

Contents

More Source Code Patching

Here are the remaining files that are modified for this tutorial:

  • MGFighter.cpp
  • MGFighter.h
  • MGGame.cpp
  • MGGame.h
  • MGInput.cpp
  • MGInput.h
  • MGMultiplayer.cpp
  • MGMultiplayer.h
  • MGWeapons.cpp
  • MGWeapons.h
  • C4NodeInfo.cpp
  • Game.txt
  • Input.txt
  • Engine.cfg

MGFighter.cpp

In FighterController::Initialize, find the line firingTime = 0;, and insert the following line:

	weaponFiring = false;	// [Huidafa]

This section should now look like this:

	movementFlags = 0;
	fighterMotion = kMotionNone;
	motionComplete = false;
	firingTime = 0;
	weaponFiring = false;	// [Huidafa]
	
	SetCollisionExclusionMask(kColliderCorpse);
	SetGroundResistanceCoefficient(0.015F);

In FighterController::FireWeapon, find the case statement block with case kWeaponPlasmaGun:, and add the following section after its closing brace:

// START: [Huidafa]
		case kWeaponManipulator:
		{
			if (!weaponFiring)		// single shot weapon
			{
				firingTime = MaxZero(firingTime + 15);	// debounced below 15ms

				long index = fighter->GetWorld()->NewControllerIndex();
				TheMessageMgr->SendMessageAll(
					CreateManipulatorMessage(
						index,
						superNode->GetWorldTransform() * position,
						worldDirection * 1.0F, GetControllerIndex()));
			}
			break;
		}
// END: [Huidafa]

Also, add the following line just before the closing brace for FighterController::FireWeapon:

	weaponFiring = true;	// [Huidafa]

The ending section of FighterController::FireWeapon should now look like this:

		case kWeaponPlasmaGun:
		{
			long ammo = fighterPlayer->GetWeaponAmmo(kWeaponPlasmaGun);
			if (ammo > 0)
			{
				fighterPlayer->SetWeaponAmmo(kWeaponPlasmaGun, ammo - 1);
				firingTime = MaxZero(firingTime + 100);
				
				long index = fighter->GetWorld()->NewControllerIndex();
				TheMessageMgr->SendMessageAll(CreatePlasmaMessage(index, superNode->GetWorldTransform() * position, worldDirection * 0.1F, GetControllerIndex()));
			}
			
			break;
		}

// START: [Huidafa]
		case kWeaponManipulator:
		{
			if (!weaponFiring)		// single shot weapon
			{
				firingTime = MaxZero(firingTime + 15);	// debounced below 15ms

				long index = fighter->GetWorld()->NewControllerIndex();
				TheMessageMgr->SendMessageAll(
					CreateManipulatorMessage(
						index,
						superNode->GetWorldTransform() * position,
						worldDirection * 1.0F, GetControllerIndex()));
			}
			break;
		}
// END: [Huidafa]
	}
	weaponFiring = true;	// [Huidafa]
}

Next, in FighterController::EndFiring add the following line just before the closing brace:

	weaponFiring = false;	// [Huidafa]

The ending section of FighterController::EndFiring should now look like this:

	fighterFlags &= ~(kFighterFiringPrimary | kFighterFiringSecondary);

	weaponFiring = false;	// [Huidafa]
}

Finally, in FighterController::SetWeapon, find the line with case kWeaponProtonCannon:, and add this new case statement block just beneath it (just before the closing brace of the switch statement):

// START: [Huidafa]
			case kWeaponManipulator:
				weaponEntity = Entity::Get(kEntityManipulator);
				break;
// END: [Huidafa]

The ending section of FighterController::SetWeapon should now look like this:

			case kWeaponProtonCannon:
				
				weaponEntity = Entity::Get(kEntityProtonCannon);
				break;

// START: [Huidafa]
			case kWeaponManipulator:
				weaponEntity = Entity::Get(kEntityManipulator);
				break;
// END: [Huidafa]
		}
		
		fighterPlayer->SetCurrentWeapon(type);
	}
}

MGFighter.h

In class FighterController, find the line with long firingTime;, and add the following line:

			bool			weaponFiring;	//	[Huidafa]

This section should now look like this:

			unsigned long		movementFlags;
			FighterMotion		fighterMotion;
			bool			motionComplete;
			long			firingTime;
			bool			weaponFiring;	//	[Huidafa]

MGGame.cpp

In the Game::Game constructor, find the line with plasmaGunEntityReg(kEntityPlasmaGun, "PlasmaGun", and add the following section on the next line:

// START: [Huidafa]
		manipulatorEntityReg(
			kEntityManipulator,
			"PlayersCard",
			kEntityPrecache,
			kControllerCollectable),
// END: [Huidafa]

Now find the line with lightningControllerReg(kControllerLightning, and add the following section on the next line:

// START: [Huidafa]
		pushButtonControllerReg(
			kControllerPushButton,
			stringTable.GetString(StringID('CTRL', 'PUSH')),
			&PushButtonController::ValidNode,
			&PushButtonController::ConstructFunction),
// END: [Huidafa]

Next, find the line with RotationController::RegisterFunctions(&rotationControllerReg), and add the following line right after it:

	PushButtonController::RegisterFunctions(&pushButtonControllerReg);	// [Huidafa]

Next, find the line with protonCannonAction(kActionProtonCannon, and add the following line right after it:

		manipulateAction(kActionManipulate),	// Manipulate action. [Huidafa]

Next, find the line with plasmaGunEntityReg.SetEntitySize, and add the following line right after it:

	manipulatorEntityReg.SetEntitySize(0.25F, 0.25F, 0.25F);	// [Huidafa]

Now find the line with Entity::RegisterEntity(&plasmaGunEntityReg), and add the following line right after it:

	Entity::RegisterEntity(&manipulatorEntityReg);	// [Huidafa]

Next, find the line with Controller::RegisterController(&lightningControllerReg, and add the following line right after it:

	Controller::RegisterController(&pushButtonControllerReg);	// [Huidafa]

Now find the line with TheInputMgr->AddAction(&plasmaGunAction, and add the following line right after it:

	TheInputMgr->AddAction(&manipulateAction);	// Manipulate action. [Huidafa]

Moving on to function Game::ConstructController, find the line with case kControllerLightning, and insert this new case after that statement block:

// START: [Huidafa]
		case kControllerPushButton:
			
			return (new PushButtonController);
// END: [Huidafa]

This section should now look like this:

		case kControllerLightning:
			
			return (new LightningController);

// START: [Huidafa]
		case kControllerPushButton:
			
			return (new PushButtonController);
// END: [Huidafa]

		case kControllerBlast:
			
			return (new BlastController);

Finally, in function Game::ConstructController, find the line with case kControllerPlasma and add the following section right after it:

// START: [Huidafa]
		case kControllerManipulator:
			
			return (new ManipulatorController);
// END: [Huidafa]

This section should now look like this:

		case kControllerPlasma:
			
			return (new PlasmaController);
		
// START: [Huidafa]
		case kControllerManipulator:
			
			return (new ManipulatorController);
// END: [Huidafa]
		
		case kControllerSoldier:
			
			return (new SoldierController);

MGGame.h

In class Game, find the declaration of plasmaGunEntityReg, and add the following line right after it:

			EntityRegistration			manipulatorEntityReg;	// [Huidafa]

This section should now look like this:

			EntityRegistration			plasmaGunEntityReg;
			EntityRegistration			manipulatorEntityReg;	// [Huidafa]
			EntityRegistration			protonCannonEntityReg;

Now find the declaration for lightningControllerReg, and add the following line right after it:

			ControllerRegistration		pushButtonControllerReg;	// [Huidafa]

This section should now look like this:

			ControllerRegistration		lightningControllerReg;
			ControllerRegistration		pushButtonControllerReg;	// [Huidafa]
			
			PropertyRegistration		jumpPropertyReg;

Finally, find the declaration for protonCannonAction, and add the following line right after it:

			WeaponAction				manipulateAction;	// Manipulate action. [Huidafa]

This section should now look like this:

			WeaponAction				protonCannonAction;
			WeaponAction				manipulateAction;	// Manipulate action. [Huidafa]
			SwitchAction				nextWeaponAction;

MGInput.cpp

In the function WeaponAction::Begin, find the statement for case kActionProtonCannon, and add the following section:

// START: [Huidafa]
			case kActionManipulate:	// Manipulate action.

				weapon = kWeaponManipulator;
				break;
// END: [Huidafa]

The end of the function should now look like this:

			case kActionProtonCannon:
				
				weapon = kWeaponProtonCannon;
				break;

// START: [Huidafa]
			case kActionManipulate:	// Manipulate action.

				weapon = kWeaponManipulator;
				break;
// END: [Huidafa]
		}
		
		TheMessageMgr->SendMessage(kAddressServer, ClientWeaponMessage(weapon));
	}
}

MGInput.h

Find the line with kActionScoreboard, and insert the following line just before it:

		kActionManipulate		= 'mani',	// Manipulate action. [Huidafa]

This section should now look like this:

		kActionManipulate		= 'mani',	// Manipulate action. [Huidafa]
		kActionScoreboard		= 'scor',

MGMultiplayer.cpp

In the function CreateEntityMessage::ConstructMessage, find the statement block that begins with case kEntityMessagePlasma, and insert the following section right after it:

// START: [Huidafa]
		case kEntityMessageManipulator:
			
			return (new CreateManipulatorMessage);
// END: [Huidafa]		

This section should now look like this:

		case kEntityMessagePlasma:
			
			return (new CreatePlasmaMessage);
// START: [Huidafa]
		case kEntityMessageManipulator:
			
			return (new CreateManipulatorMessage);
// END: [Huidafa]		
		case kEntityMessageSoldier:
			
			return (new CreateSoldierMessage);

Next, in the function Player::AcquireWeapon, add a new entry to the initialAmmo array:

	static const long initialAmmo[kWeaponCount] =
	{
		-1, -1, -1, -1, 20, 15, 50, 1	// Ammo not used for manipulator. [Huidafa]
	};

NOTE: If you have added custom weapons in other tutorials, just add the additional entry for this "weapon" to the end of this array, being careful to keep the correct number of entries to match kWeaponCount. (It might help to declare named constants for each initial ammo count if you are having trouble keeping them in order.)

Finally, in the function MultiplayerMgr::CreateEntity, find the case statement block for kEntityMessageSoldier, and insert the following new case statement block just above it:

			//	START: [Huidafa]
			case kEntityMessageManipulator:
			{
				const CreateManipulatorMessage *m =
					static_cast<const CreateManipulatorMessage *>(message);
				
				long shooter = m->GetShooterIndex();
				if (localPlayer)
				{
					const FighterController *fighterController = localPlayer->GetPlayerController();
					if ((fighterController) && (fighterController->GetControllerIndex() == shooter))
					{
						long ammo = localPlayer->GetWeaponAmmo(kWeaponManipulator);
						if (!TheMessageMgr->Server())
							localPlayer->SetWeaponAmmo(kWeaponManipulator, ammo);
						if ((localPlayer->GetCurrentWeapon() == kWeaponManipulator) && (TheStatsInterface))
							TheStatsInterface->SetPlayerAmmo(ammo);
					}
				}
				
				controller = new ManipulatorController(
					m->GetInitialPosition(),
					m->GetInitialVelocity(),
					shooter);
				entity = new Entity;
				
				break;
			}
			//	END: [Huidafa]

MGMultiplayer.h

Find the enum value declared as kEntityMessagePlasma, and insert the following line just after it:

		kEntityMessageManipulator	= 5,	// [Huidafa]

This section should now look like this:

		kEntityMessagePlasma		= 4,
		kEntityMessageManipulator	= 5,	// [Huidafa]
		kEntityMessageSoldier		= 6

Finally, find the enum declaration for kWeaponProtonCannon, and insert the following line just after it:

		kWeaponManipulator,		// Manipulator weapon. [Huidafa]

This section should now look like this:

		kWeaponProtonCannon,
		kWeaponManipulator,		// Manipulator weapon. [Huidafa]
		kWeaponCount

MGWeapons.cpp

Find the line where kPlasmaRadius is defined, and add the following line just after it:

	const float kManipulatorRadius	= 0.1F;		// [Huidafa]

Now find the function PlasmaImpactMessage::Decompress. After the closing brace of that function (which should be the end of the file), add the following section:

// START: [Huidafa]
ManipulatorController::ManipulatorController()
	:
	ShotController(kControllerManipulator, kManipulatorRadius)
{
}

ManipulatorController::ManipulatorController(
	const Point3D& position,
	const Vector3D& velocity,
	long shooter)
	:
	ShotController(
		kControllerManipulator, kManipulatorRadius, 0, position, velocity, 40.0F, shooter)
{
	lifeTime = 500;
}

ManipulatorController::~ManipulatorController()
{
}

unsigned long ManipulatorController::GetPackSize(
	unsigned long packFlags) const
{
	return (ShotController::GetPackSize(packFlags) + sizeof(ChunkHeader) * 2 + 4);
}

void ManipulatorController::Pack(
	Packer& data,
	unsigned long packFlags) const
{
	ShotController::Pack(data, packFlags);
	
	data << ChunkHeader('data', 4);
	data << lifeTime;
	
	data << TerminatorChunk;
}

void ManipulatorController::Unpack(
	Unpacker& data,
	unsigned long unpackFlags)
{
	ShotController::Unpack(data, unpackFlags);
	UnpackChunkList<ManipulatorController>(
		data,
		unpackFlags,
		&ManipulatorController::UnpackChunk);
}

bool ManipulatorController::UnpackChunk(const ChunkHeader *chunkHeader, Unpacker& data, unsigned long unpackFlags)
{
	switch (chunkHeader->chunkType)
	{
		case 'data':
			
			data >> lifeTime;
			return (true);
	}
	
	return (false);
}

void ManipulatorController::ProcessGeometryProperties(void)
{
	ShotController::ProcessGeometryProperties();

	const CollisionData *data = GetCollisionData();
	
	const Property *property = data->geometry->GetFirstProperty();
	while (property)
	{
		switch (property->GetPropertyType())
		{
			case kPropertyPushButton:
			{
				const PushButtonProperty *pushButtonProperty =
					static_cast<const PushButtonProperty *>(property);
				pushButtonProperty->GetPushButtonController()->Click();
				break;
			}

			//	[TODO] Slider and Dial
		}
		
		property = property->Next();
	}
}

ControllerMessage *ManipulatorController::ConstructMessage(ControllerMessageType type) const
{
	switch (type)
	{
		case kControllerMessageManipulatorTeleport:
			
			return (new ManipulatorTeleportMessage(GetControllerIndex()));
		
		case kControllerMessageManipulatorPick:
			
			return (new ManipulatorPickMessage(GetControllerIndex()));
	}
	
	return (nullptr);
}

void ManipulatorController::ReceiveMessage(const ControllerMessage *message)
{
	switch (message->GetControllerMessageType())
	{
		case kControllerMessageManipulatorTeleport:
		{
			const ManipulatorTeleportMessage *m = static_cast<const ManipulatorTeleportMessage *>(message);
			
			const Point3D& position = m->GetInitialPosition();
			SetInitialState(position, m->GetInitialVelocity());
			
			Node *node = GetTargetNode();
			node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * position);
			
			break;
		}
		
		case kControllerMessageManipulatorPick:
		{
			MarkingData		data;
			
			const ManipulatorPickMessage *m = static_cast<const ManipulatorPickMessage *>(message);
			
			Node *node = GetTargetNode();
			World *world = node->GetWorld();
			
			delete node;
			break;
		}
	}
}

void ManipulatorController::Preprocess(void)
{
	ShotController::Preprocess();
}

void ManipulatorController::EnterWorld(Zone *zone, const Point3D& zonePosition)
{
}

void ManipulatorController::EnterZone(Zone *zone)
{
}

void ManipulatorController::ExitZone(Zone *zone)
{
}

void ManipulatorController::Move(void)
{
	if (TheMessageMgr->Server())
	{
		if ((lifeTime -= TheTimeMgr->GetDeltaTime()) <= 0)
		{
			ManipulatorController::Destroy(GetTargetNode()->GetWorldPosition());
			return;
		}
	}
	
	ShotController::Move();
}

ShotResponseState ManipulatorController::HitGeometry(const Point3D& colliderPosition)
{
	const CollisionData *data = GetCollisionData();
	const Geometry *geometry = data->geometry;
	
	if (geometry->GetObject()->GetGeometryFlags() & kGeometryRemotePortal)
	{
		const Controller *controller = geometry->GetController();
		if ((controller) && (controller->GetControllerType() == kControllerTeleport))
		{
			const RemotePortal *portal =
				static_cast<const TeleportController *>(controller)->GetRemotePortal();
			if (portal)
			{
				// Manipulator goes through a teleporting portal
				
				Transform4D transform =
					portal->GetWorldTransform() * portal->GetRemoteTransform() * portal->GetInverseWorldTransform();
				
				Point3D position = transform * colliderPosition;
				Vector3D velocity = transform * GetCollisionVelocity();
				Point3D center = data->position + data->normal * 0.5F;
				
				TheMessageMgr->SendMessageAll(
					ManipulatorTeleportMessage(GetControllerIndex(), position, velocity, center));
				return (kShotCollided);
			}
		}
	}
	
	// Manipulator hits ordinary geometry
	
	ProcessGeometryProperties();
	TheMessageMgr->SendMessageAll(
		ManipulatorPickMessage(GetControllerIndex(), colliderPosition, data->normal));
	return (kShotDestroyed);
}

ShotResponseState ManipulatorController::HitCollider(const Point3D& colliderPosition)
{
	const CollisionData *data = GetCollisionData();
	Collider *collider = data->collider;
	
	if (collider->GetColliderClass() == kColliderProjectile)
	{
		static_cast<ShotController *>(collider)->Destroy(data->position);
		ManipulatorController::Destroy(data->position);
	}
	
	return (kShotDestroyed);
}

void ManipulatorController::Animate(void)
{
}

void ManipulatorController::Destroy(const Point3D& position)
{
	TheMessageMgr->SendMessageAll(
		ManipulatorPickMessage(GetControllerIndex(), position, (-GetInitialVelocity()).Normalize()));
}


CreateManipulatorMessage::CreateManipulatorMessage()
	:
CreateEntityMessage(kEntityMessageManipulator)
{
}

CreateManipulatorMessage::CreateManipulatorMessage(
	long controllerIndex,
	const Point3D& position,
	const Vector3D& velocity,
	long shooter)
	:
	CreateEntityMessage(kEntityMessageManipulator, controllerIndex, position)
{
	initialVelocity = velocity;
	shooterIndex = shooter;
}

CreateManipulatorMessage::~CreateManipulatorMessage()
{
}

void CreateManipulatorMessage::Compress(Compressor& data) const
{
	CreateEntityMessage::Compress(data);
	
	data << initialVelocity.x;
	data << initialVelocity.y;
	data << initialVelocity.z;
	data << (unsigned short) shooterIndex;
}

bool CreateManipulatorMessage::Decompress(Decompressor& data)
{
	if (CreateEntityMessage::Decompress(data))
	{
		unsigned short	index;
		
		data >> initialVelocity.x;
		data >> initialVelocity.y;
		data >> initialVelocity.z;
		
		data >> index;
		shooterIndex = index;
		
		return (true);
	}
	
	return (false);
}


ManipulatorTeleportMessage::ManipulatorTeleportMessage(long controllerIndex)
	:
	ControllerMessage(kControllerMessageManipulatorTeleport, controllerIndex)
{
}

ManipulatorTeleportMessage::ManipulatorTeleportMessage(
	long controllerIndex,
	const Point3D& position,
	const Vector3D& velocity,
	const Point3D& center)
	:
	ControllerMessage(kControllerMessageManipulatorTeleport, controllerIndex)
{
	initialPosition = position;
	initialVelocity = velocity;
	effectCenter = center;
}

ManipulatorTeleportMessage::~ManipulatorTeleportMessage()
{
}

void ManipulatorTeleportMessage::Compress(Compressor& data) const
{
	ControllerMessage::Compress(data);
	
	data << initialPosition.x;
	data << initialPosition.y;
	data << initialPosition.z;
	data << initialVelocity.x;
	data << initialVelocity.y;
	data << initialVelocity.z;
	data << effectCenter.x;
	data << effectCenter.y;
	data << effectCenter.z;
}

bool ManipulatorTeleportMessage::Decompress(Decompressor& data)
{
	if (ControllerMessage::Decompress(data))
	{
		data >> initialPosition.x;
		data >> initialPosition.y;
		data >> initialPosition.z;
		data >> initialVelocity.x;
		data >> initialVelocity.y;
		data >> initialVelocity.z;
		data >> effectCenter.x;
		data >> effectCenter.y;
		data >> effectCenter.z;
		
		return (true);
	}
	
	return (false);
}


ManipulatorPickMessage::ManipulatorPickMessage(long controllerIndex)
	:
	ControllerMessage(kControllerMessageManipulatorPick, controllerIndex)
{
}

ManipulatorPickMessage::ManipulatorPickMessage(
	long controllerIndex,
	const Point3D& center,
	const Vector3D& normal)
	:
	ControllerMessage(kControllerMessageManipulatorPick, controllerIndex)
{
	effectCenter = center;
	effectNormal = normal;
}

ManipulatorPickMessage::~ManipulatorPickMessage()
{
}

void ManipulatorPickMessage::Compress(Compressor& data) const
{
	ControllerMessage::Compress(data);
	
	data << effectCenter.x;
	data << effectCenter.y;
	data << effectCenter.z;
	data << effectNormal.x;
	data << effectNormal.y;
	data << effectNormal.z;
}

bool ManipulatorPickMessage::Decompress(Decompressor& data)
{
	if (ControllerMessage::Decompress(data))
	{
		data >> effectCenter.x;
		data >> effectCenter.y;
		data >> effectCenter.z;
		data >> effectNormal.x;
		data >> effectNormal.y;
		data >> effectNormal.z;
		
		return (true);
	}
	
	return (false);
}

// END: [Huidafa]

MGWeapons.h

The ManipulatorController class handles hit-testing (or picking) to determine what the user is clicking.

First, find the line where the enum constant kControllerPlasma is defined, and replace it with this section:

// START: [Huidafa]
		kControllerPlasma	= 'plas',
		kControllerManipulator	= 'mani'
// END: [Huidafa]

Next, find the line where kControllerMessagePlasmaImpact is defined, and replace it with this section:

	// START: [Huidafa]
		kControllerMessagePlasmaImpact		= 2,

		kControllerMessageManipulatorTeleport	= 0,
		kControllerMessageManipulatorPick	= 1,
		kControllerMessageManipulatorImpact	= 2
	// END: [Huidafa]

Now find the line where kEntityProtonCannon is defined, and replace it with this section:

// START: [Huidafa]
		kEntityProtonCannon		= 'pcan',
		kEntityManipulator		= 'mani'
// END: [Huidafa]

Next, find the declaration of class ShotController, and look for the declaration of function HitCollider. Insert the following line just after that declaration:

			virtual void ProcessGeometryProperties(void);	// [Huidafa]

This section should now look like this:

			virtual ShotResponseState HitGeometry(const Point3D& colliderPosition) = 0;
			virtual ShotResponseState HitCollider(const Point3D& colliderPosition) = 0;
			
			virtual void ProcessGeometryProperties(void);	// [Huidafa]
		
		public:

Finally, look for the declaration of class PlasmaImpactMessage. After the closing brace of that class, replace the remainder of the file with this section:

	//	START: [Huidafa]
	class ManipulatorController : public ShotController
	{
		friend class Game;
		
		private:
			
			long					lifeTime;
						
			ManipulatorController();
			
			bool UnpackChunk(
				const ChunkHeader *chunkHeader,
				Unpacker& data,
				unsigned long unpackFlags);
			
			virtual void ProcessGeometryProperties(void);

			ShotResponseState HitGeometry(const Point3D& colliderPosition);
			ShotResponseState HitCollider(const Point3D& colliderPosition);

		public:
			
			ManipulatorController(const Point3D& position, const Vector3D& velocity, long shooter);
			~ManipulatorController();
			
			unsigned long GetPackSize(unsigned long packFlags) const;
			void Pack(Packer& data, unsigned long packFlags) const;
			void Unpack(Unpacker& data, unsigned long unpackFlags);
			
			ControllerMessage *ConstructMessage(ControllerMessageType type) const;
			void ReceiveMessage(const ControllerMessage *message);
			
			void Preprocess(void);
			void EnterWorld(Zone *zone, const Point3D& zonePosition);
			void EnterZone(Zone *zone);
			void ExitZone(Zone *zone);
			
			void Move(void);
			
			void Animate(void);
			void Destroy(const Point3D& position);
	};

	class CreateManipulatorMessage : public CreateEntityMessage
	{
		friend class CreateEntityMessage;
		
		private:
			
			Vector3D		initialVelocity;
			long			shooterIndex;
			
			CreateManipulatorMessage();
		
		public:
			
			CreateManipulatorMessage(
				long controllerIndex,
				const Point3D& position,
				const Vector3D& velocity,
				long shooter);
			~CreateManipulatorMessage();
			
			const Vector3D& GetInitialVelocity(void) const
			{
				return (initialVelocity);
			}
			
			long GetShooterIndex(void) const
			{
				return (shooterIndex);
			}
			
			void Compress(Compressor& data) const;
			bool Decompress(Decompressor& data);
	};
	
	
	class ManipulatorTeleportMessage : public ControllerMessage
	{
		friend class ManipulatorController;
		
		private:
			
			Point3D			initialPosition;
			Vector3D		initialVelocity;
			Point3D			effectCenter;
			
			ManipulatorTeleportMessage(long controllerIndex);
		
		public:
			
			ManipulatorTeleportMessage(
				long controllerIndex,
				const Point3D& position,
				const Vector3D& velocity,
				const Point3D& center);
			~ManipulatorTeleportMessage();
			
			const Point3D& GetInitialPosition(void) const
			{
				return (initialPosition);
			}
			
			const Vector3D& GetInitialVelocity(void) const
			{
				return (initialVelocity);
			}
			
			const Point3D& GetEffectCenter(void) const
			{
				return (effectCenter);
			}
			
			void Compress(Compressor& data) const;
			bool Decompress(Decompressor& data);
	};
	
	
	class ManipulatorPickMessage : public ControllerMessage
	{
		friend class ManipulatorController;
		
		private:
			
			Point3D			effectCenter;
			Vector3D		effectNormal;
			
			ManipulatorPickMessage(long controllerIndex);
		
		public:
			
			ManipulatorPickMessage(long controllerIndex, const Point3D& center, const Vector3D& normal);
			~ManipulatorPickMessage();
			
			const Point3D& GetEffectCenter(void) const
			{
				return (effectCenter);
			}
			
			const Vector3D& GetEffectNormal(void) const
			{
				return (effectNormal);
			}
			
			void Compress(Compressor& data) const;
			bool Decompress(Decompressor& data);
	};
}

#endif
// END: [Huidafa]

C4NodeInfo.cpp

Finally, adjust the height of the Get Info dialog to be a bit taller, to accommodate our additional PushButtonController settings:

NodeInfoWindow::NodeInfoWindow(Node *node, Editor *editor)
	:
	Window(
		812.0F, 800.0F,	//	Adjust window height to be taller. [Huidafa]
		nullptr,
		kWindowTitleBar | kWindowCenter)

Game.txt

Find the line with 'LTNG' "Lightning" and then insert the following section right after it:

	'PUSH' "Push Button"
	{
		'PBSC' "Push Button Sync"
		'PBCK' "Push Button Click"
		'PBST' "State:"
		{
			'PBSU' "Up"
			'PBSD' "Down"
			'PBSB' "Disabled"
			'PBRU' "Rollover Up"
			'PBRD' "Rollover Down"
		}
		'PBBE' "Behavior:"
		{
			'SPRG' "Spring"
			'STKY' "Sticky"
			'TGGL' "Toggle"
		}
		'PBCU' "Up Glow Color"
		'PBCD' "Down Glow Color"
		'PBCB' "Disabled Glow Color"
		'PBCR' "Rollover Up Glow Color"
		'PBCS' "Rollover Down Glow Color"
		'PBSG' "Sound Range (meters):"
		'PBSU' "Up Sound"
		'PBSD' "Down Sound"
		'PBSB' "Disabled Sound"
		'PBSR' "Rollover Up Sound"
		'PBSS' "Rollover Down Sound"
		'PBUS' "Up Speed (meters/sec):"
		'PBDS' "Down Speed (meters/sec):"
		'PBDP' "Down Position Overshoot %"
		'PBBS' "Is Blinking"
		'PBBR' "Blink Rate (blinks/sec):"
		'PBBC' "Blink Color"
		'PBBP' "Blink Period %"
	}

Input.txt

Look for the line with 'plas' "Plasma Gun", and then insert the following line right after it:

'mani' "Manipulator"

This section should now look like this:

'plas' "Plasma Gun"
'mani' "Manipulator"
'cann' "Proton Cannon"

Importing the string tables

Now import the modified string tables for Game.txt and Input.txt:

  • Run C4, and access the console by pressing the tilde key.
  • In the console command line, type istring and press enter.
  • Select Game.txt from the list and click OK.
  • Next, type istring again, and press enter.
  • Select Input.txt from the list and click OK.
  • Quit C4

Engine.cfg

Find the line with bind "=" %next;, and insert the following line right after it:

bind "0" %mani;

This section should now look like this:

bind "=" %next;
bind "0" %mani;
bind "W" %frwd;

Build

Now build C4 and make sure there were no build errors.

Trying the Demo

Download an archive with demo models and textures here:

Here are some sample sounds for the push buttons as well:

After the downloads complete, extract the contents of the archive PushButtonController.rar

Now move the contents of the mdl, tex, and wld sub-directories into the corresponding directories in your C4\Data directory.

Then, move the "Pick.wav" and "NoPick.wav" files in the PushButtonSounds archive into your C4\Data\wav directory.

Finally, we're ready to try out in-game "Push Button Controls with Geometry"!

Run C4.exe, click "Start New Game", and then choose the DemoFanAP1 world to play in.

Be sure to pick up the C4 Engine "Player's Card":

Image:PlayersCardFront.jpg

Press the zero key "0" to select the "manipulator weapon", to be able to click on push buttons. (You can still switch back to a weapon that shoots ammo with the usual key bindings.)

Walk inside of the building containing the fan area, and with the "manipulator weapon" selected, click the push buttons on the "Fan AP-1" panel:

Image:FanAP1Closeup6.jpg

Custom panels

To study how to create your own user interface panels, open up FanAP-1T.wld in the world editor.

To study how to connect your user interface panels to objects in the world, open up DemoFanAP1.wld in the world editor.

(This section needs to be expanded to show step-by-step instructions for using the PushButtonController in the world editor.)

Future Enhancements

Part III of this article will show how to add a proximity trigger which automatically puts down the player's weapon as they approach a button panel, centers and freezes the camera rotation, (to make it easier to move the mouse over the buttons), and change the cursor to a stylized arrow cursor. Part III of this article will also show how to restore the previously selected weapon when the player leaves the immediate panel area.

Personal tools