Geizer¶
A water or honey geizer that can be frozen using Leif's Freeze and it can elevate a Dropplet ice cube. The oscillation and time to stay frozen are configurable with the option to conditionally activate it when another NPCControl hit
is true.
Data Arrays¶
data[0]
: The optional suffix to append to the path when loading the Geizer prefab after converting the number to string, defaults to empty string if not present or if it's <= 0 which is the normal water looking one. NOTE: only a value of 1 is allowed under normal gameplay because it's the only one that gives a valid path (the honey looking geizer), an exception will be thrown if the path is invaliddata[1]
: The map entity id whose NPCControl'shit
should be true for the geizer to be active. This is optional, it can be ommitted or be -1 to have the geizer always be active.data[2]
: If 1 and the map.lastwater
exists (the water Hazards), the spout of the main geizer object will continually be set to the map.lastwater
y position every updates where the geizer isn't frozendata[3]
: If it's not 0, the MeshRenderer of the frozen geizer spout doesn't get enabled when frozen. If it's 1, the geizer position will be set to be entity.startpos
+ the geizer up vector * 10.0 on the first LateUpdate where it isincamera
andstartlife
is below 20.0 (this means the geizer will start under the floor or inside its wall instead of slowly retracting when the map is done loading). This is optional, it remains enabled if it doesn't exists and nothing special will happen on LateUpdate.data[4]
: If it's not 0, the geizer won't be frozen when it collides with anIceRadius
. This is optional, it will be frozen if it doesn't existvectordata[0].x
: 1/6 of the geizer's oscillation's curve frequency in htzvectordata[0].y
: The geizer's oscillation's curve magnitudevectordata[0].z
: The amount of time in frames to keep the geizer frozen when it collides with an ice collider (this is trippled if the Extra Freeze medal is equipped)dialogues[2].x
: The local scale of the entity.model
will be multiplied by the 1/10 of this, but this is optional and the scale will not change if it is 0.1 or belowdialogues[2].y
: If not 0, theelectime
of the GlowTrigger when the entity.originalid
is theElectroPlatform
AnimID. This is optional and the value defaults is 260.0
Setup¶
- entity.
rigid
gets effectively locked by disabling gravity, making it kinematic and freezing all constraints - isStatic on the gameObject is set to false
nointeract
is set to true- entity.
soundonpause
is set to true - The gameObject layer to 0 (Default).
- The
scol
is disabled actionfrequency
: is initialised to 2 elemenst :- 0: Random.Range(1.0, 10.0) (this is a random phase shift to the geizer oscillation)
- 1: 0.0 (this is a DeltaTime accumulator for use in the geizer oscillation)
actioncooldown
: set to -1100.0 (meaning the first update cycle will be treated as if we had an expired cooldown since more than 1 update cycle)- A geizer prefab is instantiated from the path
Prefabs/Objects/Geizer
followed bydata[0]
if applicable. The geizer is childed to the entity'ssprite
and has its local position set to Vector3.zero. internaltransform
is initialised to 5 elements:- 0: The first child of the geizer's root (The root of the main geizer object)
- 1: The second child of the geizer's root (the root of the frozen geizer object)
- 2: The first child of the geizer's first child (the top of the main geizer object)
- 3: The second child of the geizer's first child (the spout at the bottom of the main geizer's object)
- 4: The first child of the geizer's second child (the frozen spout at the bottom of the frozen geizer object)
- entity.
model
scale is multiplied by a 1/10 ofdialogues[2].x
- entity.
alwaysactive
is set to true - entity.
model
tag is set toPlatformNoClock
- If entity.
originalid
is theElectroPlatform
AnimID, a GlowTrigger is added on the first child of themodel
:getactivecolorfromstart
is set to trueparent
is set to this NPCControlglowparts
is initialised to a single element corresponding to the MeshRenderer of the first child of themodel
electime
is initialised to 260.0 unlessdialogues[2].y
exists and isn't 0 where it will take that value instead
Update¶
The internaltransform[3]
(the bottom spout of the main geizer object) gets its local scale set to Vector3.one. If this is the first Update, initialrender
is initialised to a single element being the MeshRenderer of the spout of the geizer object. This MeshRenderer is then disabled.
From there, the geizer can be active or inactive. It's active if data[1]
doesn't exist, is -1 or the map entity with the id being data[1]
exists with a hit
value of true. It is inactive otherwise. This changes the rest of the update cycle.
Inactive updates¶
In the case where the geizer shouldn't be active, GeizerBreak is called if the actioncooldown
hasn't expired yet (it is also set to 0.0 in this case). This is followed by the position being set to a lerp from the existing one to the entity's startpos
- the transform's up vector * 10.0 with a factor of 0.025 * the game's frametime. This basically makes the geizer slowly retract itself such that it's not visible because it would be under the floor or inside a wall.
Active update¶
If the startlife
is above 20.0 frames and hit
is false, hit
is set to true and the ParticleSystem of internaltransform[3]
(the spout of the main geizer) is set to play which is some water splashing.
The position is set to lerp from the existing one to the entity.startpos
with a factor of framestep * 0.025. This makes the geizer slowly show itself if it wasn't active earlier.
From there, there are 3 cases: the actioncooldown
hasn't expired, it expired last cycle or it has been expired since more than 1 update cycle. This cooldown controls the time left for the geizer to remain frozen before thawing via GeizerBreak. If it expired, it means the geizer isn't frozen.
ActionCooldown hasn't expired yet¶
- If
data[3]
is 0 or doesn't exist,internalrender[0]
(the MeshRenderer of the spout of the frozen geizer object) gets enabled - The entity.
sound
is set to not loop anymore - The
actioncooldown
is decremented by framestep - The
internaltransform[1]
(the frozen geizer object) local position is set to Vector3.zero unless there's less than 100.0 frames left on theactioncooldown
in which case, it's set to (Random.Range(-0.05, 0.05), 0.0, 0.0). This applies a somewhat random offset to the frozen geizer every update cycle when there's 100.0 frames left.
actioncooldown
expired since the last Update cycle¶
If the actioncooldown
expired since the last cycle (tested by checking it is 0.0 or below, but above -1000.0):
internaltransform[0]
is activated (the main geizer object)internaltransform[1]
is deactivated (the frozen geizer object)internaltransform[3]
is activated (the spout at the bottom of main geizer object)- The
boxcol
is enabled if it is present - If
startlife
is 15.0 or above, GeizerBreak is called - The ParticleSystem of
internaltransform[3]
(the spout at the bottom of the water object) is set to play which is some water splashing. - The entity.
sound
is set to no longer loop actioncooldown
is set to -1100.0 which changes further update cycles to have the cooldown expired since more than 1 cycle.
actioncooldown
expired more than 1 Update cycle ago¶
If the actioncooldown
expired more than 1 cycle ago (checked by being below -1000.0):
internaltransform[2]
(the top of the main geizer object) is set to rotate by 5.0 degrees in the z axis (this is the vertical one due to the pivot point being rotated)internaltransform[3]
(the spout at the bottom of the water object) is set to rotate by -5.0 degrees in the y axis- The
boxcol
center is set to (0.0, 3.0 + the root geizer main object y position, 0.0) which elevates it slightly above its position - The geizer's root position (the parent of
internaltransform[0]
) is set to a lerp from the existing one to the entity.startpos
+ the normalized up vector of the transform * Mathf.Sin(actionfrequency[1]
*vectordata[0].x
+actionfrequency[0]
) *vectordata[0].y
with a factor of the 1/10 of the game's frametime. This oscillates the geizer's:vectordata[0].y
is the magnitude of the sine wave, it tells half of the full range between the lowest point the geizer will go and the higestactionfrequency[1]
is the time in seconds accumulated since SetUp so this scales the Sin through timeactionfrequency[2]
is a phase shift that's been determined randomly on SetUpvectordata[0].x
is 1/6 of the frequency
actionfrequency[1]
is incremented by Time.deltaTime- If the entity's
sound
wasn't playing, PlaySound is called with theWaterfall1
clip at 0.075 volume and the entity.sound
is set to loop - If
data[2]
is present and 1 and there is a map.lastwater, the y component of theinternaltransform[3]
position (the spout at the bottom of the main geizer object) is set to map.lastwater
y position. This basically means the spout will be positioned on the map's water Hazards level. - The
internalrender[0]
(the MeshRenderer of the spout of the frozen geizer object) gets disabled and its position set to theinternaltransform[3]
one (the spout at the bottom of the main geizer object)
LateUpdate (Not a dummy
, incamera
is true and hit
is false)¶
internaltransform[3]
(the spout of the main geizer object) has its scale set to Vector3.One- A Raycast is performed from the geizer position + (0.0, 10.0, 0.0) headed down with max 10.0 distance only accepting layers of
Ground
orNoDigGround
. - If
attacking
is false (this is the first applicable LateUpdate cycle), it is set to true after a Fader component is added to the gameObject with:- forcestayonpause to true
- childtied to true
- fadedistance to 0.0
- pivotoffset to (0.0, the point.y of the collision done earlier or 0.0 if there wasn't a collision - entity.
startpos
, 0.0)
- If
data[3]
is 1 andstartlife
is less than 20.0, the position is set to the entity.startpos
- the up vector of the geizer * 10.0. This places the geizer such that it will be under the floor / inside its wall very soon after map load - If there was a collision with the raycast earlier, the position of
internaltransform[3]
(the spout of the main geizer object) is set to the hit point of the raycast and it also gets childed to the entity.sprite
. This basically ensures the spout is placed where the ground actually is and to ensure it doesn't move with the geizer, it's childed in such a way that it is a sibling of the geizer so it stays there independently of the geizer movements
OnTriggerEnter¶
There are 4 branches here:
- The other gameObject tag is
Icecle
,Icefall
orIceRadius
anddata[4]
doesn't exist or is 0 - The other gameObject tag is
BeetleHorn
orBeetleDash
and theactioncooldown
hasn't expired yet (meaning the geizer is frozen) - The other gameObject tag is
DroppletCube
and theactioncooldown
expired (meaning the geizer isn't frozen) - The other gameObject tag is
PFollower
or it's the player
Ice collider logic¶
- If the
moveobj
is present (meaning a Dropplet ice cube is on the geizer):- LaunchObject is called with it using a random vector between (0.0, -15.0, 0.0) and (0.0, 15.0, 0.0)
- The parent of the ice cube is set to the current map
- ServerGeizer is called onm the
moveobj
Hornable which setups the cube to be on the geizer snapped to it
- If the
actioncooldown
expired (meaning the geizer isn't frozen):- The
boxcol
is disabled if present internaltransform[0]
(the main geizer object) gets disabledinternaltransform[3]
(the spout of the main geizer object) gets disabledinternaltransform[1]
(the frozen geizer object) gets enabledinternaltransform[4]
(the spout of the frozen geizer object) gets enabled except if the current map isUpperSnekGeizerRoom
where it is disabled- The entity.
sound
is stopped and placed at the begining of the playback - The
Freeze
sound is played at 0.5 volume
- The
- The
actioncooldown
gets set tovectordata[0].z
, but if the Extra Freeze medal is equipped, the value is multiplied by 3 before assigning it
Kabbu collider logic while the geizer is frozen¶
- The player entity
hitwall
is set to true - The
actioncooldown
is set to 0.0 - GeizerBreak is called
- The
boxcol
is disabled if it is present
Dropplet cube collider logic while the geizer isn't frozen¶
- If the
moveobj
is present (there was already a dropplet ice cube on the geizer):- LaunchObject is called with it using a random vector between (-5.0, -10.0, 0.0) and (5.0, 10.0, 0.0)
- ServerGeizer is called onm the
moveobj
Hornable which setups the cube to be on the geizer snapped to it
- The RigidBody of the other's parent gets its velocity zeroed out without gravity in kinematic mode
- The parent of the other dropplet is set to
internaltransform[0]
(The main geizer object) moveobj
is assigned to the other dropplet- The
ingeizer
of themoveobj
's Hornable gets set to thisGeizer
Player or player follower collider logic¶
The other gameObject gets rooted to the scene.
Hazards.OnObjectStay¶
If the NPCControl passed has this type, it returns true which allows it to stay in collision with the Hazards.
MainManager.RefreshEntities¶
This logic only matters if the method was called as a result of MapControl.Start, MapControl.RefreshInsides and BattleControl.ReturnToOverworld.
If hit
is true and internaltransform
is not empty, all ParticleSystem under internaltransform[3]
(the spout at the bottom of the main geizer's object) are played.
GeizerBreak¶
This is a private method that only applies to this object type and its job is to shatter a frozen geizer.
- The
IceBreak
sound clip is played at the geizer position + Vector3.Up at 0.75 volume IceShatter
particles are played at (the geizer x position, 0.0 - entity.startpos
- 0.45, the geizer z position - 0.5) and the scale of those particles is set to (2.0, 3.0, 1.0)- For all playerdata entities, if any was childed to any transform under this geizer, the entity gets rooted to the scene
Effects of the PlatformNoClock
tag¶
The same than PathPlatform except of course, the player entities noclock
is always false so mostly the childing logic remains. In additiona however, it's what allows Dropplet ice cube to be childed to the geizer via their Hornable component when the ice cube collides with the geizer.