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.
shadowis always kept enabled and when the dropplet is frozen, its position is set to be offscreen immediately - If 1, entity.
shadowis only enabled whenhitis false (no ice cube exists) and when the dropplet is frozen, the entity.rigidis 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.
shadowis only enabled whenhitis 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 +startposis 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 theWaterSplashsound when a dropplet collides with a GameObject with layerGroundorNoDigGround
Setup¶
actionfrequencyis initialised to 3 elements left at 0.0 each- entity.
hasshadowis set to true - entity.
overrideshadowis set to true - entity.
alwaysactiveis set to true - entity.
activeonpauseis set to true - entity.
shadowis 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
DroppletCubewith a size of Vector3.one * 0.01. All collisions with it and the player.entity.ccolor the outer BoxCollider are ignored
After:
vectordata[0]is incremented by the entity.startposinternalparticleis initialised to a single element which is the SpriteBounce of a instance ofPrefabs/Objects/WaterBubblewhich is then childed to the entity.spriteand has a local position of Vector3.up / 2- entity.
rigidgets its gravity enabled with rotation frozen and entity.ongroundset 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.startposwhich 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.
rigidvelocity gets zeroed out without kinematic mode
From there the following always happen:
- LockRgid(false) is called on the entity
- entity.
rigidgets 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
IceMeltsound 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:
hitis 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
ingeizergets set to null - Its DialogueAnim is enabled with a
shrinkspeedof 0.1 and atargetscaleof (1.5, 1.5, 1.5) - Its RigidBody gets its velocity zeroed out and put out of kinematic mode
- If the
collisionammountis less than 2, theFreezesound is played at 0.6 volume collisionammountis 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,actioncooldownis set todata[3] - The entity.
rigidis 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.
rigidis not in kinematic mode, the following depends oninternalparticle[0]:- If it is null, it is initialised to a
WaterSplashParticleSystem 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
pauseand thestartlifeis over 60.0 frames, theWaterSplashsound is played at this object position with a volume of the entity sound distance adjusted for the volume settings and multipled byvectordata[1].zif it wasn't 0.0. - The entity.
rigidis put in kinematic mode - entity.
spriteis 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
IcecleorIceRadiuscollider to collide with it during this short period):- The position is set to be offscreen at (0.0, -2000.0, 0.0)
- entity.
spriteis 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.
rigidgets its velocity zeroed without kinematic and with gravity - entity.
ongroundis set to false - If entity.
originalmapis 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:
hitis set to false which makes the game no longer recognise the ice cube existsIceShatterparticle 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
IceBreaksound 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
pushercollider 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.hitwallis set to true and ShatterDroppletIce is called on the NPCControlPlatformorPlatformNoClock: 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.
boxcolare 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.
moveobjis set to null - ingeizer is set to null