Dropplet¶
A water dropplet that can be frozen by Leif's Freeze which then turns it into an ice cube that can be moved by launching it using Kabbu's Horn Slash. The push velocity vector when launched is configurable and the object also supports many optional features, see the data array definition for details.
Data Arrays¶
data[0]
: The time interval in frames between main Updates where the ice cube and dropplet are managed. This allows to throttle those updatesdata[1]
: If above 0, the max distance in units the cube can away of entity.startpos
+vectordata[0]
. If the ice cube goes further, it will be shattered via ShatterDroppletIce. This isn't enforced if the value is 0 or belowdata[2]
: There are 3 possible meanings:- If 0, entity.
shadow
is always kept enabled and when the dropplet is frozen, its position is set to be offscreen immediately - If 1, entity.
shadow
is only enabled whenhit
is false (no ice cube exists) and when the dropplet is frozen, the entity.rigid
is put in kinematic mode and the dropplet position is set to be offscreen in 0.1 seconds. This also allowsdata[3]
to take effect if applicable - Any other value: If 1, entity.
shadow
is only enabled whenhit
is false (no ice cube exists) and when the dropplet is frozen, its position is set to be offscreen immediately
- If 0, entity.
data[3]
: Ifdata[2]
is 1 and this value is above 0, the time period in frames that the ice cube is allowed to exist before shattering via ShatterDroppletIce on its own. This is optional, this feature is disabled if it doesn't exist or is 0 or below. NOTE: specifying a value above 0 whiledata[2]
isn't 1 is considered invalid and will cause the ice cube to shatter on the first throttled updatedata[4]
: If 1, the ice cube can only be launched in the 8 cardinal directions relative to the camera instead of having free movement in x and zvectordata[0]
: Ifdata[1]
is above 0, this is an offset position where its value +startpos
is the center of the range where the ice cube is allowed to stay with a max distance ofdata[1]
. If the ice cube goes out of this range, it is shattered via ShatterDroppletIcevectordata[1].x
: The x and z velocity of the ice cube when launchedvectordata[1].y
: The y velocity of the ice cube when launchedvectordata[1].z
: If not 0.0, the volume multiplier of theWaterSplash
sound when a dropplet collides with a GameObject with layerGround
orNoDigGround
Setup¶
actionfrequency
is initialised to 3 elements left at 0.0 each- entity.
hasshadow
is set to true - entity.
overrideshadow
is set to true - entity.
alwaysactive
is set to true - entity.
activeonpause
is set to true - entity.
shadow
is ensured to be created if it wasn't
From there, internaltransform
is initialised to one element which is an instance of the Prefabs/Objects/icecube
prefab which is then childed to the map. Some adjustements are performed on it:
- The layer is set to 13 (NoDigGround)
- The local scale is set to Vector3.one * 1.5
- A RigidBody is added on it with kinematic and rotation frozen with a mass of 100000.0
- The BoxCollider present at the root of it gets enabled without trigger at size Vector3.one
- A DialogueAnim is added with a startsize and targetpos of Vector3 * 1.5, a targetpos of Vector3.zero and a speed of 0.01
- A Hornable is added with a push of (
vectordata[1].x
,vectordata[1].y
) created from this NPCControl with breakondash and without pusher. - The first child's tag is set to
DroppletCube
(this is the actual cube inside the outer BoxCollider) - A trigger BoxCollider is added to the
DroppletCube
with a size of Vector3.one * 0.01. All collisions with it and the player.entity.ccol
or the outer BoxCollider are ignored
After:
vectordata[0]
is incremented by the entity.startpos
internalparticle
is initialised to a single element which is the SpriteBounce of a instance ofPrefabs/Objects/WaterBubble
which is then childed to the entity.sprite
and has a local position of Vector3.up / 2- entity.
rigid
gets its gravity enabled with rotation frozen and entity.onground
set to false
Update¶
If the entity.rigid
isn't in kinematic mode, its y velocity will be set to -10.0 and RefreshShadow is called on the entity. This forces the dropplet to drop down at a constant speed.
If the actioncooldown
hasn't expired yet, it is decremented by the game's frametime. Otherwise LockRigid(false, false) is called on the entity. This allows to unlock the entity.rigid
once a dropplet is ready to be dropped.
If the entity.shadow
is present, it will get enabled if data[2]
is 0 or hit
is false and it will get disabled otherwise.
What follows is logic that depends on the actionfrequency
3 different cooldowns.
actionfrequency
cooldowns¶
Here is what the different array elements mean:
actionfrequency[0]
: UNUSED (It always stays to -1100 and never changes)actionfrequency[1]
: The max cooldown before the dropplet is moved to entity.startpos
which is hardcoded to 600.0actionfrequency[2]
: The cooldown before doing any of this logic which means it serve as a throttle. Defaults todata[2]
Nothing in this section happens if actionfrequency
hasn't expired yet besides the value being decremented by framestep. This effectively throttles main updates to only happen every data[0]
frames.
If we aren't being throttled, this is where actionfrequency[1]
comes into play. If it hasn't expired yet and the entity y position is -25.0 or above, then actionfrequency[1]
is decremented by framestep. Otherwise, it means the cooldown expired, the dropplet dropped to \< -25 or it was placed offscreen by OnTriggerEnter as a result of touching the ground or being frozen. In that case, the dropplet is reset back to its starting position by doing the following:
- The position is set to entity.
startpos
actionfrequency[1]
is reset to 600.0 which allows the cycle to restart- entity.
rigid
velocity gets zeroed out without kinematic mode
From there the following always happen:
- LockRgid(false) is called on the entity
- entity.
rigid
gets its rotation frozen
If hit
is false or we are in a pause
or minipause
, this section is done as the rest only concerns the ice cube when unpaused.
If data[1]
is above 0 and internaltransform[0]
(the actual ice cube) is more than data[1]
away from entity.startpos
+ vectordata[0]
, it is shattered via ShatterDroppletIce.
We only proceed if data[3]
exists and is above 0.
This is where the new actioncooldown
value is managed. It was already decremented earlier so the only logic here is when it goes below 100.0 every 3 frames and when it expires.
If it's below 100.0 every 3 frames:
- The
IceMelt
sound effect is played atinternaltransform[0]
(the actual ice cube) with the volume being that transform's sound distance * 2.0 adjusted by the game's volume settings. - The DialogueAnim of the ice cube targetscale is set to Vector3.zero to make it shrink to nothing at a speed of 0.01
If it's expired, the ice cube is shattered via ShatterDroppletIce and ServerGeizer is called on the ice cube's Hornable.
LateUpdate (Non dummy
, the entity is incamera
and not iskill
)¶
Normally under these circumstances, If the y position is less than the map.ylimit
, the the position is set to the entity.startpos
and DeathSmoke particles are played at the entity.sprite
position if it isn't dead
and the animid isn't negative (it is defined).
However, Dropplet
are exempt from the logic above so they aren't bound by the map.ylimit
.
OnTriggerEnter main logic¶
This does nothing if the other gameObject tag isn't Icecle
or IceRadius
.
If hit
is true, the IceShatter
particle is played at the internaltransform[0]
position and the IceBreak
sound is played at the same position with the volume being the sound distance of the position adjusted for the game's volume * 1.5. Finally, it also causes ServerGeizer to be called on the internaltransform[0]
's Hornable.
The following occurs:
hit
is set to trueactionfrequency[0]
is set to -1100.0actionfrequency[1]
is set to 600.0internaltransform[0]
gets some adjustements:- Its position gets set to the other position + (0.0, 0.25, 0.0)
- It gets childed to the map
- Its local scale gets set to Vector3.zero
- Its Hornable
ingeizer
gets set to null - Its DialogueAnim is enabled with a
shrinkspeed
of 0.1 and atargetscale
of (1.5, 1.5, 1.5) - Its RigidBody gets its velocity zeroed out and put out of kinematic mode
- If the
collisionammount
is less than 2, theFreeze
sound is played at 0.6 volume collisionammount
is incremented- If
data[2]
is 1 (otherwise, the position is set to offscreen at (0.0, -2000.0, 0.0)):- If
data[3]
exists and is above 0,actioncooldown
is set todata[3]
- The entity.
rigid
is put in kinematic mode - The position is set to offscreen at (0.0, -2000.0, 0.0) in 0.1 seconds
- If
actionfrequency[2]
is set todata[0]
OnTriggerEnter if the other gameObject layer is Ground
or NoDigGround
¶
This does nothing if the other gameObject tag is DroppletPass
(This would have happened with a BreakableRock, but the tag gets overriden to Object
later so in practice, this can't happen under normal gameplay).
This also does nothing if actionfrequency[1]
is 595.0 or higher which means the first 6.0 frames after reseting the spawn cooldown. This is because it prevents any collisions after the first one since once the first collision happen, the Dropplet will be allowed to go through the collider for 1/6th of a second which is ~6.0 frames.
actionfrequency[1]
is set to 600.0- If the entity.
rigid
is not in kinematic mode, the following depends oninternalparticle[0]
:- If it is null, it is initialised to a
WaterSplash
ParticleSystem being played at this object position with angles of (-90.0, 0.0, 0.0) and it is then parented to the map. - If it wasn't null, its position is set to this object position and Play is called on it
- If it is null, it is initialised to a
- If we aren't in a
pause
and thestartlife
is over 60.0 frames, theWaterSplash
sound is played at this object position with a volume of the entity sound distance adjusted for the volume settings and multipled byvectordata[1].z
if it wasn't 0.0. - The entity.
rigid
is put in kinematic mode - entity.
sprite
is disabled - HidePos is invoked in 1/6th of a second which will do the following (the dropplet is allowed to go through the wall at this point, but it won't be visible and it allows an
Icecle
orIceRadius
collider to collide with it during this short period):- The position is set to be offscreen at (0.0, -2000.0, 0.0)
- entity.
sprite
is enabled again
actionfrequency[0]
is set to -1110.0actionfrequency[2]
is set todata[0]
MainManager.RefreshEntities¶
This logic only matters if the method was called as a result of MapControl.Start, MapControl.RefreshInsides and BattleControl.ReturnToOverworld.
If data[2]
is 0 or hit
is false:
- entity.
rigid
gets its velocity zeroed without kinematic and with gravity - entity.
onground
is set to false - If entity.
originalmap
is the current one, the dropplet is childed to the map
ShatterDroppletIce¶
This is a public method that is practically called in a wide variety of places, but it only contains logic pertinent to this object type. It shatters the ice cube by doing the following:
hit
is set to false which makes the game no longer recognise the ice cube existsIceShatter
particle is played at the ice cube's position + (0.0, 1.0, 0.0) with angles (-90.0, 0.0, 0.0) for 1.0 second- The
IceBreak
sound effect is played at the ice cube's position with the volume being the ice cube sound distance adjusted by the game volume settings - The position of the ice cube is set to be offscreen (0.0, -1000.0, 0.0) the next frame
- The RigidBody of the ice cube is put into kinematic mode
- The ice cube is childed to the map
- The DialogueAnim of the ice cube is enabled
Hornable¶
This component is only used for this object type on internaltransform[0]
(the actual ice cube) making all its logic exclusive to a dropplet. Its job is to manage the actual movement of the ice cube when using Kabbu's Horn Slash or when it is on a Geizer.
For the sake of brevety, the component won't be fully documented, but a summary of the methods is provided here.
To properly add the Hornable, SetUp must be called and in practice, it's always called with the same parameters on this NPCContol's SetUp:
- pushammount is (
vectordata[1].x
,vectordata[1].y
) - pusherenabled is false, but then overriden to true on the component's Start (prevents it to be affected by a
pusher
collider on an NPC) - dashbreak is true (this is unused so it does nothing)
- parent is this NPCControl
On its Start, the main thing that happens is the tag is set to Hornable
(allows PlayerControl to get a green ! emoticon when getting 2.5 or lower distance from the grass for 5 frames), a HelpArrow is setup with a cyan color and a rotater object is created childed to the ice cube (this is used later when data[4]
gets involved).
On its LateUpdate, the main things that happens are:
- Some logic where the ice cube is visually displaced when the ice cube is in a Geizer
- The frictions are managed here (basically full friction when at rest, no frictions otherwise)
- Enforcing that the cube cannot be more than 150.0 frames in the air or its x and z velocity will be zeroed out and forced to be on the ground
On its OnTriggerEnter, there are multiple possible interactions depending on the other collider tag:
BeetleHorn
: This means the cube will be launched. The launch is done by using pushammount.x for the x and z axises and pushammount.y for the y axis as the new velocity adjusted by the actual direction the player is at so it launches away from it. However, this can be skewed ifdata[4]
exists and is 1 where the actual direction is snapped to the closest 8 cardinal directions relative to the camera (this is done using the rotater object as it's set to look at the player)IceBreak
: the player.entity.hitwall
is set to true and ShatterDroppletIce is called on the NPCControlPlatform
orPlatformNoClock
: The ice cube can get childed to the platform (or Geizer) so it moves with it. This is also done in OnCollisionEnter. This is undone in OnTriggerExit when applicable
On its OnTriggerStay, any Pusher
collider is enforced here and the ground state is reset when the other collider is layer 8 or 13 (Ground
or NoDigGround
). This is also done in OnCollisionEnter and OnCollisionStay
ServerGeizer¶
This public method of the component is important because it setup some changes when the ice cube gets on a Geizer. It does nothing however if ingeizer
is null.
- The collisions between the root collider or the actual ice cube collider and the ingeizer.
boxcol
are temporarilly ignored for 5.0 seconds - The ice cube is childed to the map
- The root RigidBody velocity is zeroed out with gravity and without kinematic mode
- ingeizer.
moveobj
is set to null - ingeizer is set to null