Cannon

Detailed implementation of the cannon

Cannon structure

  • ClientCannon - handles most animations, e.g. the coroutine for the main flight
  • ServerCannon - ?
  • IClientCannonHandler - handler that can be used to launch different kinds of objects. Currently there’s only one implementation - ClientCannonPlayerHandler.

Launch process

LaunchProjectile is the main entry point to the coroutine. This is in response to receiving a server event to launch the projectile.

private IEnumerator LaunchProjectile(GameObject _objectToLaunch, Transform _target)
{
    IClientCannonHandler handler = this.GetHandler(_objectToLaunch);
    if (handler != null)
    {
        handler.Launch(_objectToLaunch);
    }
    _objectToLaunch.transform.SetParent(null, true);
    if (this.m_onLaunchedCallback != null)
    {
        this.m_onLaunchedCallback(_objectToLaunch);
    }
    IEnumerator animation = this.m_cannon.m_animation.Run(_objectToLaunch, _target);
    while (animation.MoveNext())
    {
        yield return null;
    }
    if (handler != null)
    {
        handler.Land(_objectToLaunch);
    }
    this.m_cannon.EndCannonRoutine(_objectToLaunch);
    if (handler != null)
    {
        IEnumerator exit = handler.ExitCannonRoutine(_objectToLaunch, _target.position, _target.rotation);
        while (exit.MoveNext())
        {
            yield return null;
        }
    }
    yield break;
}
  1. Call handler.Launch. This sets up the player - disabling controls, setting kinematic, disabling collider. See ClientCannonPlayerHandler.Launch:

    public void Launch(GameObject _obj)
     {
     	if (_obj != null)
     	{
     		this.m_inCannon = false;
     		this.m_controls.enabled = false;
     		this.m_controls.Motion.SetKinematic(true);
     		Collider collider = _obj.RequireComponent<Collider>();
     		collider.enabled = false;
     	}
     }
    
  2. Detach the transform of the object being launched from the cannon.

  3. Call the m_onLaunchedCallback if exists. This callback is only registered by ClientCannonCosmeticDecisions, so it’s cosmetics only (animations, particles, audio).

  4. Start the ProjectileAnimation animation coroutine. See ProjectileAnimation::Run; this manually animates the projectile by positioning it according to a parabola.

  5. Call the handler.Land function; see ClientCannonPlayerHandler.Land:

    public void Land(GameObject _obj)
     {
     	if (_obj != null)
     	{
     		this.m_controls.enabled = true;
     		if (this.m_controls.GetComponent<PlayerIDProvider>().IsLocallyControlled())
     		{
     			this.m_controls.Motion.SetKinematic(false);
     		}
     		Collider collider = _obj.RequireComponent<Collider>();
     		collider.enabled = true;
     		this.m_controls.GetComponent<ClientPlayerControlsImpl_Default>().ApplyImpact(this.m_controls.transform.forward.XZ() * 2f, 0.2f);
     	}
     }
    
  6. Call m_cannon.EndCannonRoutine (note this is on the Cannon, not ClientCannon). This is registered by only ServerCannon to call ServerCannon.EndCannonRoutine:

    public void EndCannonRoutine(GameObject _obj)
     {
     	this.m_flying = false;
     	IServerCannonHandler handler = this.GetHandler(_obj);
     	if (handler != null)
     	{
     		handler.ExitCannonRoutine(_obj);
     	}
     }
    

    This handler is the IServerCannonHandler, which also has only one implementation: ServerCannonPlayerHandler.ExitCannonRoutine:

    public void ExitCannonRoutine(GameObject _obj)
    {
    	_obj.GetComponent<Rigidbody>().isKinematic = false;
    	GroundCast groundCast = _obj.RequestComponent<GroundCast>();
    	if (groundCast != null)
    	{
    		groundCast.ForceUpdateNow();
    	}
    	ServerWorldObjectSynchroniser serverWorldObjectSynchroniser = _obj.RequestComponent<ServerWorldObjectSynchroniser>();
    	if (serverWorldObjectSynchroniser != null)
    	{
    		serverWorldObjectSynchroniser.ResumeAllClients(false);
    	}
    }
    
  7. Start the coroutine from handler.ExitCannonRoutine; see ClientCannonPlayerHandler.ExitCannonRoutine:

    public IEnumerator ExitCannonRoutine(GameObject _player, Vector3 _exitPosition, Quaternion _exitRotation)
     {
     	this.m_inCannon = false;
     	this.m_controls.AllowSwitchingWhenDisabled = false;
     	this.m_controls = null;
     	this.m_playerIdProvider = null;
     	if (_player != null)
     	{
     		DynamicLandscapeParenting dynamicParenting = _player.RequestComponent<DynamicLandscapeParenting>();
     		if (dynamicParenting != null)
     		{
     			dynamicParenting.enabled = true;
     		}
     		yield return null;
     	}
     	if (_player != null)
     	{
     		ClientWorldObjectSynchroniser synchroniser = _player.RequireComponent<ClientWorldObjectSynchroniser>();
     		while (synchroniser != null && !synchroniser.IsReadyToResume())
     		{
     			yield return null;
     		}
     		if (synchroniser != null)
     		{
     			synchroniser.Resume();
     		}
     	}
     	yield break;
     }
    

    Even though this is a coroutine, it’s really only necessary to resume the ClientWorldObjectSynchroniser. The first yield appears unnecessary.

What is GroundCast?

  • Seems to be responsible for handling sticking to the ground. Doesn’t seem all that important to understand it; just need to call ForceUpdateNow(). Also, the client part doesn’t call this, so maybe it’s not necessary here?

What is ServerWorldObjectSynchroniser and ClientWorldObjectSynchroniser?

  • It’s for physics synchronization. When the chef is in flight it doesn’t need to be synchronized. Afterwards we must re-enable it.
  • However, physics synchronization is disabled for local session anyway, so this is probably still not important.

What is DynamicLandscapeParenting?

  • It doesn’t matter what it is… we just need to enable it.

Load process

ServerCannon’s Load doesn’t really do anything except sending a message. We begin with ClientCannon.Load.

public void Load(GameObject _obj)
{
    this.m_loadedObject = _obj;
    this.m_exitPosition = _obj.transform.position;
    this.m_exitRotation = _obj.transform.rotation;
    IClientCannonHandler handler = this.GetHandler(_obj);
    if (handler != null)
    {
        handler.Load(_obj);
    }
    _obj.transform.position = this.m_cannon.m_attachPoint.position;
    _obj.transform.rotation = this.m_cannon.m_attachPoint.rotation;
    _obj.transform.SetParent(this.m_cannon.m_attachPoint, true);
    if (this.m_onLoadedCallback != null)
    {
        this.m_onLoadedCallback(_obj);
    }
}

Pretty simple here - sets all the transforms, parents, and calls ClientCannonPlayerHandler.Load:

public void Load(GameObject _player)
{
    this.m_controls = _player.RequireComponent<PlayerControls>();
    this.m_controls.AllowSwitchingWhenDisabled = true;
    this.m_controls.ThrowIndicator.Hide();
    this.m_playerIdProvider = _player.RequireComponent<PlayerIDProvider>();
    this.m_inCannon = true;
    DynamicLandscapeParenting dynamicLandscapeParenting = _player.RequestComponent<DynamicLandscapeParenting>();
    if (dynamicLandscapeParenting != null)
    {
        dynamicLandscapeParenting.enabled = false;
    }
    ClientWorldObjectSynchroniser clientWorldObjectSynchroniser = _player.RequestComponent<ClientWorldObjectSynchroniser>();
    if (clientWorldObjectSynchroniser != null)
    {
        clientWorldObjectSynchroniser.Pause();
    }
}

It’s mostly the inverse of the end of the launch process.

Unload process

ClientCannon.Unload:

public IEnumerator Unload(GameObject _obj, Vector3 _exitPosition, Quaternion _exitRotation)
{
    if (_obj != null)
    {
        _obj.transform.position = _exitPosition;
        _obj.transform.rotation = _exitRotation;
        _obj.transform.SetParent(null, true);
    }
    this.m_cannon.EndCannonRoutine(_obj);
    IClientCannonHandler handler = this.GetHandler(_obj);
    if (handler != null)
    {
        IEnumerator exit = handler.ExitCannonRoutine(_obj, _exitPosition, _exitRotation);
        while (exit.MoveNext())
        {
            yield return null;
        }
    }
    if (this.m_onUnloadedCallback != null)
    {
        this.m_onUnloadedCallback(_obj);
    }
    yield break;
}

Basically the same as the launch process except there’s no launching.