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'shitshould 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.lastwaterexists (the water Hazards), the spout of the main geizer object will continually be set to the map.lastwatery 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 isincameraandstartlifeis 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.modelwill 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, theelectimeof the GlowTrigger when the entity.originalidis theElectroPlatformAnimID. This is optional and the value defaults is 260.0
Setup¶
- entity.
rigidgets effectively locked by disabling gravity, making it kinematic and freezing all constraints - isStatic on the gameObject is set to false
nointeractis set to true- entity.
soundonpauseis set to true - The gameObject layer to 0 (Default).
- The
scolis 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/Geizerfollowed bydata[0]if applicable. The geizer is childed to the entity'sspriteand has its local position set to Vector3.zero. internaltransformis 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.
modelscale is multiplied by a 1/10 ofdialogues[2].x - entity.
alwaysactiveis set to true - entity.
modeltag is set toPlatformNoClock - If entity.
originalidis theElectroPlatformAnimID, a GlowTrigger is added on the first child of themodel:getactivecolorfromstartis set to trueparentis set to this NPCControlglowpartsis initialised to a single element corresponding to the MeshRenderer of the first child of themodelelectimeis initialised to 260.0 unlessdialogues[2].yexists 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.
soundis set to not loop anymore - The
actioncooldownis 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 theactioncooldownin 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
boxcolis enabled if it is present - If
startlifeis 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.
soundis set to no longer loop actioncooldownis 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
boxcolcenter 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].ywith a factor of the 1/10 of the game's frametime. This oscillates the geizer's:vectordata[0].yis 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].xis 1/6 of the frequency
actionfrequency[1]is incremented by Time.deltaTime- If the entity's
soundwasn't playing, PlaySound is called with theWaterfall1clip at 0.075 volume and the entity.soundis 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.lastwatery 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
GroundorNoDigGround. - If
attackingis 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 andstartlifeis 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,IcefallorIceRadiusanddata[4]doesn't exist or is 0 - The other gameObject tag is
BeetleHornorBeetleDashand theactioncooldownhasn't expired yet (meaning the geizer is frozen) - The other gameObject tag is
DroppletCubeand theactioncooldownexpired (meaning the geizer isn't frozen) - The other gameObject tag is
PFolloweror it's the player
Ice collider logic¶
- If the
moveobjis 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
moveobjHornable which setups the cube to be on the geizer snapped to it
- If the
actioncooldownexpired (meaning the geizer isn't frozen):- The
boxcolis 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 isUpperSnekGeizerRoomwhere it is disabled- The entity.
soundis stopped and placed at the begining of the playback - The
Freezesound is played at 0.5 volume
- The
- The
actioncooldowngets 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
hitwallis set to true - The
actioncooldownis set to 0.0 - GeizerBreak is called
- The
boxcolis disabled if it is present
Dropplet cube collider logic while the geizer isn't frozen¶
- If the
moveobjis 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
moveobjHornable 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) moveobjis assigned to the other dropplet- The
ingeizerof 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
IceBreaksound clip is played at the geizer position + Vector3.Up at 0.75 volume IceShatterparticles 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.