2026-08-24 15:17:53 +02:00
|
|
|
import { Terrain, type Terrain as Ground } from "../../../engine/world/Terrain"
|
|
|
|
|
import { Mesh, type Mesh as Geometry } from "../../../engine/scene/Mesh"
|
|
|
|
|
import type { Material } from "../../../engine/render/Material"
|
|
|
|
|
import type { ActorDefinition } from "../../../engine/scene/Actor"
|
|
|
|
|
import { Mob, type MobState } from "../Mob"
|
2026-08-07 19:11:43 +02:00
|
|
|
import { ellipsoid, nextRand, wanderHeading } from "./mobkit"
|
|
|
|
|
|
|
|
|
|
// Everything about the frog: squat, ground-bound, sits then springs a ballistic hop.
|
|
|
|
|
|
2026-08-24 15:17:53 +02:00
|
|
|
export type Frog = ActorDefinition<MobState, Ground>
|
|
|
|
|
|
|
|
|
|
export namespace Frog {
|
|
|
|
|
export function create(material: Material): Frog {
|
|
|
|
|
const mesh = Mesh.create()
|
|
|
|
|
build(mesh)
|
|
|
|
|
return {
|
|
|
|
|
prototype: {
|
|
|
|
|
groups: [{ mesh, material }],
|
|
|
|
|
radius: 0.7,
|
|
|
|
|
minY: -0.7,
|
|
|
|
|
maxY: 1.3,
|
|
|
|
|
},
|
|
|
|
|
update,
|
|
|
|
|
transform: Mob.transform,
|
|
|
|
|
collider: (state) => Mob.collider(state, 0.7, 0.6, state.grounded),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-08 14:15:32 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-07 19:11:43 +02:00
|
|
|
const LEASH = 5
|
|
|
|
|
const REST_MIN = 0.7
|
|
|
|
|
const REST_SPAN = 1.8
|
|
|
|
|
const HOP_SPEED = 1.6
|
|
|
|
|
const HOP_IMPULSE = 3.2
|
|
|
|
|
const GRAVITY = 14
|
|
|
|
|
|
2026-08-24 15:17:53 +02:00
|
|
|
function build(mesh: Geometry): void {
|
2026-08-07 19:11:43 +02:00
|
|
|
// Wide squat body, two eye bumps on the top-front, two hind haunches. UVs:
|
|
|
|
|
// the frog texture is green skin on the left, a dark eye tone on the right.
|
|
|
|
|
ellipsoid(mesh, 0, 0.26, 0, 0.5, 0.28, 0.52, 6, 4, 0, 0.68, 0, 1)
|
|
|
|
|
ellipsoid(mesh, 0.24, 0.5, 0.26, 0.13, 0.13, 0.13, 4, 3, 0.75, 0.98, 0, 1)
|
|
|
|
|
ellipsoid(mesh, -0.24, 0.5, 0.26, 0.13, 0.13, 0.13, 4, 3, 0.75, 0.98, 0, 1)
|
|
|
|
|
ellipsoid(mesh, 0.3, 0.2, -0.26, 0.2, 0.2, 0.26, 4, 3, 0, 0.68, 0, 1)
|
|
|
|
|
ellipsoid(mesh, -0.3, 0.2, -0.26, 0.2, 0.2, 0.26, 4, 3, 0, 0.68, 0, 1)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-24 15:17:53 +02:00
|
|
|
function update(mob: MobState, dt: number, terrain: Ground): void {
|
2026-08-07 19:11:43 +02:00
|
|
|
if (mob.grounded) {
|
|
|
|
|
mob.timer -= dt
|
|
|
|
|
mob.position.y = Terrain.height(terrain, mob.position.x, mob.position.z)
|
|
|
|
|
if (mob.timer > 0) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Launch a hop: pick a heading (pulled homeward past the leash), then convert
|
|
|
|
|
// it into a forward+upward ballistic velocity.
|
|
|
|
|
mob.heading = wanderHeading(mob, LEASH, 0.9)
|
|
|
|
|
mob.vx = Math.sin(mob.heading) * HOP_SPEED
|
|
|
|
|
mob.vz = Math.cos(mob.heading) * HOP_SPEED
|
|
|
|
|
mob.vy = HOP_IMPULSE
|
|
|
|
|
mob.grounded = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
mob.vy -= GRAVITY * dt
|
|
|
|
|
mob.position.x += mob.vx * dt
|
|
|
|
|
mob.position.y += mob.vy * dt
|
|
|
|
|
mob.position.z += mob.vz * dt
|
|
|
|
|
const ground = Terrain.height(terrain, mob.position.x, mob.position.z)
|
|
|
|
|
if (mob.position.y <= ground && mob.vy < 0) {
|
|
|
|
|
mob.position.y = ground
|
|
|
|
|
mob.vx = 0
|
|
|
|
|
mob.vy = 0
|
|
|
|
|
mob.vz = 0
|
|
|
|
|
mob.grounded = true
|
|
|
|
|
mob.timer = REST_MIN + nextRand(mob) * REST_SPAN
|
|
|
|
|
}
|
|
|
|
|
}
|