feat(packages): ludic.aim - what the crosshair is on: a ray through the middle of the screen against upright cylinders widened by a fixed angular forgiveness, the nearest one the ground does not hide, reach floored at three metres, and which thing the use key means (that, nothing and say why, or the nearest); the probe of the ground's own answer against the aim; the camera, the ground and the body through a port
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
24a35f1174
commit
dc71fc986c
10 changed files with 399 additions and 0 deletions
60
packages/ludic.aim/README.md
Normal file
60
packages/ludic.aim/README.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# ludic.aim
|
||||
|
||||
What the crosshair is on, and which thing the use key means. The crosshair is the exact middle of
|
||||
the screen and the projection is symmetric, so the ray through it is the camera's position along its
|
||||
forward - no unprojection. Each candidate is an upright cylinder widened by a fixed angular
|
||||
forgiveness, and the pick is the nearest one the ray enters that the ground does not hide. Uses
|
||||
[`ludic.base`](../ludic.base/README.md) and nothing else.
|
||||
|
||||
```ludic
|
||||
import "ludic.aim"
|
||||
```
|
||||
|
||||
The package keeps no list of things: the game offers every candidate each frame (`aim_offer`), keeps
|
||||
the object behind the id it gave, and draws the rim and the prompt.
|
||||
|
||||
## The rule it keeps
|
||||
|
||||
**The crosshair decides WHICH, the distance decides WHETHER** (`aim_use(kind)`):
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| under the crosshair and in reach | `AIM_USE_THIS` - that, whatever else is nearer |
|
||||
| under the crosshair and too far, within `AIM_USE_MAX` (10 m) | `AIM_USE_NOTHING` - and the prompt says why |
|
||||
| nothing of that kind aimed at, or aimed past 10 m | `AIM_USE_NEAREST` - proximity is right again |
|
||||
|
||||
Every reach is floored at `AIM_REACH_MIN` (3 m) by `aim_reach_of(reach)`: a per-kind reach chosen
|
||||
against its model was never chosen against standing between two props and looking at one. Anything
|
||||
that asks "am I close enough to this" asks `aim_reach_of`.
|
||||
|
||||
## The port
|
||||
|
||||
```ludic
|
||||
export port AimView {
|
||||
x, y, z, fx, fy, fz, fov, # the camera (the ray)
|
||||
ground: fn(float, float) -> float,
|
||||
dry: fn(float, float) -> bool, # land, for the probe's viewpoints
|
||||
body_x, body_z # distances and reach are the body's, not the camera's
|
||||
}
|
||||
```
|
||||
|
||||
Every member has a default: a camera 1.5 m up at the origin looking down -z over flat ground.
|
||||
|
||||
## API
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `aim_begin()`, `aim_offer(kind, id, x, y0, y1, z, r, reach, far) -> bool` | a frame's candidates: true when this one is now the pick (`reach` `AIM_NEVER` is never in reach) |
|
||||
| `aim_kind()` (`AIM_NONE` for nothing), `aim_id()`, `aim_dist()`, `aim_in_reach()`, `aim_t()`, `aim_off_centre()` | the pick |
|
||||
| `aim_use(kind)` -> `AIM_USE_THIS` / `_NOTHING` / `_NEAREST`, `aim_reach_of(reach)` | the use key |
|
||||
| `aim_ray_cyl(cx, y0, y1, cz, r)`, `aim_slack_m(dist)`, `aim_clear_to(x, y, z)`, `aim_range(x, z)`, `aim_miss(cx, cz, r)` | the geometry, for a game's own probes |
|
||||
| `aim_probe_ground(px, pz, look) -> bool`, `aim_probe_seen()`, `aim_probe_blocked()`, `aim_probe_disagree()` | from a point and a ring round it, lines at 60, 120 and 200 m on 36 bearings: the ground's own answer against `aim_clear_to`; `look(x, y, z)` puts the game's camera there |
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
ludic test packages/ludic.aim
|
||||
```
|
||||
|
||||
A camera over flat ground with a ridge: the ray against a cylinder, the forgiveness, near before
|
||||
far, the ground hiding a thing, the reach floor and the three answers for the use key, and the probe.
|
||||
11
packages/ludic.aim/index.ludic
Normal file
11
packages/ludic.aim/index.ludic
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# ludic.aim - the crosshair is the exact middle of the screen, so the ray through it is the camera's
|
||||
# position along its forward. Each candidate is an upright cylinder widened by a fixed angular
|
||||
# forgiveness; the pick is the nearest one the ray enters that the ground does not hide.
|
||||
module ludic_aim uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "ports.ludic"
|
||||
import "ray.ludic"
|
||||
import "pick.ludic"
|
||||
import "use.ludic"
|
||||
import "probe.ludic"
|
||||
6
packages/ludic.aim/package.ludic
Normal file
6
packages/ludic.aim/package.ludic
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# ludic.aim - what the crosshair is on: a ray through the middle of the screen against upright
|
||||
# cylinders with an angular forgiveness, the ground in front of them, reach floors, and which thing
|
||||
# the use key means. Uses ludic.base and nothing else. See README.md.
|
||||
package "ludic.aim"
|
||||
version "0.1.0"
|
||||
kind source
|
||||
55
packages/ludic.aim/pick.ludic
Normal file
55
packages/ludic.aim/pick.ludic
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# pick.ludic - one target a frame: the game offers every candidate, and the nearest one the ray enters
|
||||
# that the ground does not hide is the pick. Near things occlude far ones for free, which is what a
|
||||
# player means by "I am looking at that one".
|
||||
export const AIM_NONE: int = 0
|
||||
export const AIM_REACH_MIN: float = 3.0 # about as far as a hand goes with a step into it
|
||||
export const AIM_USE_MAX: float = 10.0 # past this the crosshair is for looking, not doing
|
||||
export const AIM_NEVER: float = -1.0 # a reach that is never in reach (an animal without a camera)
|
||||
|
||||
var am_kind: int = 0
|
||||
var am_id: int = -1
|
||||
var am_t: float = 10000.0
|
||||
var am_dist: float = 0.0
|
||||
var am_reach: bool = false
|
||||
var am_off: float = 1.0
|
||||
|
||||
# The crosshair decides WHICH; the distance decides only WHETHER. Every reach is lifted to a floor of
|
||||
# AIM_REACH_MIN, because a per-kind reach chosen against its model was never chosen against standing
|
||||
# between two props and looking at one.
|
||||
export function aim_reach_of(reach: float) -> float { return Math.max(reach, AIM_REACH_MIN) }
|
||||
|
||||
export function aim_begin() -> void {
|
||||
am_kind = AIM_NONE
|
||||
am_id = -1
|
||||
am_t = 10000.0
|
||||
am_dist = 0.0
|
||||
am_reach = false
|
||||
am_off = 1.0
|
||||
}
|
||||
|
||||
# A candidate of `kind` (the game's own, not AIM_NONE) and `id`: an upright cylinder at (x, z) from y0
|
||||
# to y1 of radius r, within `far` of the body, in reach within `reach` (floored; AIM_NEVER never).
|
||||
# True when it is now the pick.
|
||||
export function aim_offer(kind: int, id: int, x: float, y0: float, y1: float, z: float, r: float, reach: float, far: float) -> bool {
|
||||
let d = aim_range(x, z)
|
||||
if d > far { return false }
|
||||
let rr = r + aim_slack_m(d)
|
||||
let hit = aim_ray_cyl(x, y0, y1, z, rr)
|
||||
if hit < 0.0 or not (hit < am_t) { return false }
|
||||
if not aim_clear_to(x, (y0 + y1) * 0.5, z) { return false }
|
||||
am_t = hit
|
||||
am_off = aim_miss(x, z, rr)
|
||||
am_kind = kind
|
||||
am_id = id
|
||||
am_dist = d
|
||||
am_reach = reach >= 0.0 and d < aim_reach_of(reach)
|
||||
return true
|
||||
}
|
||||
|
||||
export function aim_kind() -> int { return am_kind }
|
||||
export function aim_id() -> int { return am_id }
|
||||
export function aim_dist() -> float { return am_dist }
|
||||
export function aim_in_reach() -> bool { return am_reach }
|
||||
export function aim_t() -> float { return am_t }
|
||||
# how well centred the pick is: 0 dead centre, 1 at its (forgiven) edge
|
||||
export function aim_off_centre() -> float { return am_off }
|
||||
22
packages/ludic.aim/ports.ludic
Normal file
22
packages/ludic.aim/ports.ludic
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# ports.ludic - what the aim asks the game: the camera, the ground, and where the body stands.
|
||||
# Unbound, a camera 1.5 m up at the origin looks down -z over flat ground, with the body under it.
|
||||
export port AimView {
|
||||
x: fn() -> float = fn aim__zero
|
||||
y: fn() -> float = fn aim__eye
|
||||
z: fn() -> float = fn aim__zero
|
||||
fx: fn() -> float = fn aim__zero
|
||||
fy: fn() -> float = fn aim__zero
|
||||
fz: fn() -> float = fn aim__ahead
|
||||
fov: fn() -> float = fn aim__fov # vertical, radians
|
||||
ground: fn(float, float) -> float = fn aim__flat
|
||||
dry: fn(float, float) -> bool = fn aim__dry # land, for the probe's viewpoints
|
||||
body_x: fn() -> float = fn aim__zero # distances and reach are the body's, not the camera's
|
||||
body_z: fn() -> float = fn aim__zero
|
||||
}
|
||||
|
||||
function aim__zero() -> float { return 0.0 }
|
||||
function aim__eye() -> float { return 1.5 }
|
||||
function aim__ahead() -> float { return -1.0 }
|
||||
function aim__fov() -> float { return 1.0 }
|
||||
function aim__flat(x: float, z: float) -> float { return 0.0 }
|
||||
function aim__dry(x: float, z: float) -> bool { return true }
|
||||
61
packages/ludic.aim/probe.ludic
Normal file
61
packages/ludic.aim/probe.ludic
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# probe.ludic - does the aim agree with the ground about what it hides? From (px, pz) and a ring of
|
||||
# eight points 250 m round it, at 60, 120 and 200 m on 36 bearings, the answer worked out by walking
|
||||
# the ground under each line against aim_clear_to. Lines the ground merely grazes are left out: that
|
||||
# is the probe's sampling against the aim's, not a disagreement. Moves the camera (AimView's) through
|
||||
# `look`, which the game binds to put its camera at a point.
|
||||
var am_seen: int = 0
|
||||
var am_blocked: int = 0
|
||||
var am_disagree: int = 0
|
||||
|
||||
export function aim_probe_seen() -> int { return am_seen }
|
||||
export function aim_probe_blocked() -> int { return am_blocked }
|
||||
export function aim_probe_disagree() -> int { return am_disagree }
|
||||
|
||||
# true when at least one line is hidden and the aim disagrees with the ground on at most one in fifty
|
||||
export function aim_probe_ground(px: float, pz: float, look: fn(float, float, float) -> void) -> bool {
|
||||
am_seen = 0
|
||||
am_blocked = 0
|
||||
am_disagree = 0
|
||||
for sp in 0 .. 9 {
|
||||
var sx = px
|
||||
var sz = pz
|
||||
if sp > 0 {
|
||||
let sa = float(sp - 1) / 8.0 * 6.2831853
|
||||
sx = px + Math.sin(sa) * 250.0
|
||||
sz = pz + Math.cos(sa) * 250.0
|
||||
}
|
||||
if AimView.dry(sx, sz) { am_probe_from(sx, AimView.ground(sx, sz) + 1.6, sz, look) }
|
||||
}
|
||||
return am_blocked >= 1 and not (am_disagree > am_seen / 50)
|
||||
}
|
||||
|
||||
function am_probe_from(sx: float, sy: float, sz: float, look: fn(float, float, float) -> void) -> void {
|
||||
for di in 0 .. 3 {
|
||||
var dist = 60.0
|
||||
if di == 1 { dist = 120.0 }
|
||||
if di == 2 { dist = 200.0 }
|
||||
for b in 0 .. 36 {
|
||||
let bearing = float(b) / 36.0 * 6.2831853
|
||||
let ex = sx + Math.sin(bearing) * dist
|
||||
let ez = sz + Math.cos(bearing) * dist
|
||||
let ey = AimView.ground(ex, ez) + 0.4
|
||||
let truth = am_probe_gap(sx, sy, sz, ex, ey, ez, 0.5, 10000.0)
|
||||
if am_probe_gap(sx, sy, sz, ex, ey, ez, -0.5, 0.5) and not truth { continue }
|
||||
look(sx, sy, sz)
|
||||
am_seen += 1
|
||||
let clear = aim_clear_to(ex, ey, ez)
|
||||
if not clear { am_blocked += 1 }
|
||||
if clear == truth { am_disagree += 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# somewhere between the ends (excluded), the ground stands between lo and hi above the line
|
||||
function am_probe_gap(sx: float, sy: float, sz: float, ex: float, ey: float, ez: float, lo: float, hi: float) -> bool {
|
||||
for k in 2 .. 38 {
|
||||
let t = float(k) / 40.0
|
||||
let gap = AimView.ground(Math.lerp(sx, ex, t), Math.lerp(sz, ez, t)) - Math.lerp(sy, ey, t)
|
||||
if gap > lo and gap < hi { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
67
packages/ludic.aim/ray.ludic
Normal file
67
packages/ludic.aim/ray.ludic
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# ray.ludic - the ray through the crosshair against an upright cylinder, the forgiveness around a
|
||||
# body, and the ground in front of a point
|
||||
# t along the ray where it enters the cylinder at (cx, cz), radius r, from y0 up to y1; or -1
|
||||
export function aim_ray_cyl(cx: float, y0: float, y1: float, cz: float, r: float) -> float {
|
||||
let ox = AimView.x() - cx
|
||||
let oz = AimView.z() - cz
|
||||
let dx = AimView.fx()
|
||||
let dy = AimView.fy()
|
||||
let dz = AimView.fz()
|
||||
let a = dx * dx + dz * dz
|
||||
var t0 = -10000.0
|
||||
var t1 = 10000.0
|
||||
if a < 0.000001 {
|
||||
if ox * ox + oz * oz > r * r { return -1.0 } # straight up or down: inside or nothing
|
||||
} else {
|
||||
let b = 2.0 * (ox * dx + oz * dz)
|
||||
let disc = b * b - 4.0 * (a * (ox * ox + oz * oz - r * r))
|
||||
if disc < 0.0 { return -1.0 }
|
||||
t0 = (-b - Math.sqrt(disc)) / (2.0 * a)
|
||||
t1 = (-b + Math.sqrt(disc)) / (2.0 * a)
|
||||
}
|
||||
let cy = AimView.y()
|
||||
if Math.abs(dy) > 0.000001 {
|
||||
let ta = (y0 - cy) / dy
|
||||
let tb = (y1 - cy) / dy
|
||||
t0 = Math.max(t0, Math.min(ta, tb))
|
||||
t1 = Math.min(t1, Math.max(ta, tb))
|
||||
} else if cy < y0 or cy > y1 { return -1.0 }
|
||||
if t0 > t1 or t1 < 0.0 { return -1.0 }
|
||||
return Math.max(t0, 0.0)
|
||||
}
|
||||
|
||||
# How much wider than itself a thing is treated as at `dist` metres: a fixed angle (twelve pixels of
|
||||
# a 1080-tall screen), so a branch twenty metres off can be aimed at and the near ones are not easy
|
||||
# to hit by accident
|
||||
export function aim_slack_m(dist: float) -> float { return dist * (12.0 / 540.0) * Math.tan(AimView.fov() * 0.5) }
|
||||
|
||||
# metres from the body to (x, z)
|
||||
export function aim_range(x: float, z: float) -> float {
|
||||
let dx = x - AimView.body_x()
|
||||
let dz = z - AimView.body_z()
|
||||
return Math.sqrt(dx * dx + dz * dz)
|
||||
}
|
||||
|
||||
# Is the line from the camera to a point clear of the ground? Without it the crosshair reads through a
|
||||
# rise; within arm's length nothing is hidden, or a prop on a slope hides behind its own slope.
|
||||
export function aim_clear_to(x: float, y: float, z: float) -> bool {
|
||||
let cx = AimView.x()
|
||||
let cy = AimView.y()
|
||||
let cz = AimView.z()
|
||||
if (x - cx) * (x - cx) + (z - cz) * (z - cz) < 9.0 { return true }
|
||||
for s in 1 .. 10 {
|
||||
let f = float(s) / 10.0
|
||||
if AimView.ground(Math.lerp(cx, x, f), Math.lerp(cz, z, f)) - 0.35 > Math.lerp(cy, y, f) { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
# how far the ray passes from a body's axis as a share of its width: 0 dead centre, 1 at the edge
|
||||
export function aim_miss(cx: float, cz: float, r: float) -> float {
|
||||
let ox = AimView.x() - cx
|
||||
let oz = AimView.z() - cz
|
||||
let a = Math.sqrt(AimView.fx() * AimView.fx() + AimView.fz() * AimView.fz())
|
||||
if a < 0.0001 { return 0.0 }
|
||||
let proj = (ox * AimView.fx() + oz * AimView.fz()) / a
|
||||
return Math.sqrt(Math.max(ox * ox + oz * oz - proj * proj, 0.0)) / Math.max(r, 0.01)
|
||||
}
|
||||
101
packages/ludic.aim/tests/aim_test.ludic
Normal file
101
packages/ludic.aim/tests/aim_test.ludic
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# aim_test.ludic - a camera 1.5 m up looking down -z over flat ground with a ridge 42 m out: the ray
|
||||
# against a cylinder, the forgiveness, near before far, the ground hiding a thing, the reach floor,
|
||||
# which thing the use key means, and the probe agreeing with the ground
|
||||
import "ludic.aim"
|
||||
import "ludic.base"
|
||||
program AimTest {
|
||||
numbers float
|
||||
|
||||
var cx: float = 0.0
|
||||
var cy: float = 1.5
|
||||
var cz: float = 0.0
|
||||
var fy: float = 0.0
|
||||
function fk_x() -> float { return cx }
|
||||
function fk_y() -> float { return cy }
|
||||
function fk_z() -> float { return cz }
|
||||
function fk_fy() -> float { return fy }
|
||||
function fk_fz() -> float { return -Math.sqrt(1.0 - fy * fy) }
|
||||
# a 6 m ridge along z = -42, thirty metres across, the whole width of the view
|
||||
function fk_ground(x: float, z: float) -> float { return 6.0 * Math.max(0.0, 1.0 - Math.abs(z + 42.0) / 15.0) }
|
||||
function fk_look(x: float, y: float, z: float) -> void {
|
||||
cx = x
|
||||
cy = y
|
||||
cz = z
|
||||
}
|
||||
bind AimView { x: fn fk_x, y: fn fk_y, z: fn fk_z, fy: fn fk_fy, fz: fn fk_fz, ground: fn fk_ground }
|
||||
|
||||
function fresh() -> void {
|
||||
cx = 0.0
|
||||
cy = 1.5
|
||||
cz = 0.0
|
||||
fy = 0.0
|
||||
aim_begin()
|
||||
}
|
||||
|
||||
test "the ray enters a cylinder in front and misses one to the side or behind" {
|
||||
fresh()
|
||||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, -10.0, 0.5), 9.5, 0.01)
|
||||
expect(aim_ray_cyl(3.0, 0.0, 2.0, -10.0, 0.5) < 0.0)
|
||||
expect(aim_ray_cyl(0.0, 0.0, 2.0, 10.0, 0.5) < 0.0)
|
||||
expect(aim_ray_cyl(0.0, 0.0, 1.0, -10.0, 0.5) < 0.0)
|
||||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, 0.0, 0.5), 0.0, 0.001)
|
||||
}
|
||||
|
||||
test "a thin thing far off is still aimable, by an angle and not a metre" {
|
||||
fresh()
|
||||
expect(aim_slack_m(20.0) > aim_slack_m(5.0) * 3.9)
|
||||
expect(aim_offer(1, 7, 0.25, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_off_centre() > 0.5)
|
||||
fresh()
|
||||
expect(not aim_offer(1, 7, 3.0, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
}
|
||||
|
||||
test "the nearest thing the ray enters is the pick, whatever order they are offered in" {
|
||||
fresh()
|
||||
expect(aim_offer(1, 1, 0.0, 0.0, 2.0, -20.0, 0.5, 1.5, 30.0))
|
||||
expect(aim_offer(1, 2, 0.0, 0.0, 2.0, -8.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(1, 3, 0.0, 0.0, 2.0, -15.0, 0.5, 1.5, 30.0))
|
||||
expect_eq(aim_id(), 2)
|
||||
expect_near(aim_dist(), 8.0, 0.01)
|
||||
expect(not aim_offer(2, 4, 0.0, 0.0, 2.0, -25.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(2, 5, 0.0, 0.0, 2.0, -3.0, 0.5, 1.5, 2.0))
|
||||
}
|
||||
|
||||
test "the ground in front hides a thing, and nothing within arm's length is hidden" {
|
||||
fresh()
|
||||
expect(not aim_clear_to(0.0, 1.0, -60.0))
|
||||
expect(aim_clear_to(0.0, 1.0, -30.0))
|
||||
expect(not aim_offer(1, 1, 0.0, 0.0, 2.0, -60.0, 2.0, 1.5, 80.0))
|
||||
cy = -5.0
|
||||
expect(aim_clear_to(0.0, -5.0, -2.0))
|
||||
}
|
||||
|
||||
test "the crosshair decides which, the distance whether: every reach is floored at three metres" {
|
||||
fresh()
|
||||
expect_near(aim_reach_of(1.5), AIM_REACH_MIN, 0.001)
|
||||
expect_near(aim_reach_of(3.4), 3.4, 0.001)
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -2.6, 0.3, 1.5, 30.0)
|
||||
expect(aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_THIS)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -6.0, 0.3, 1.5, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_NOTHING)
|
||||
expect_eq(aim_use(3), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 3.0, -20.0, 0.3, 1.5, 30.0)
|
||||
expect_eq(aim_use(1), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(2, 9, 0.0, 0.0, 2.0, -2.0, 0.3, AIM_NEVER, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_kind(), 2)
|
||||
}
|
||||
|
||||
test "the probe finds the bank and agrees with the ground about every line it hides" {
|
||||
fresh()
|
||||
expect(aim_probe_ground(0.0, 0.0, fn fk_look))
|
||||
expect(aim_probe_seen() > 100)
|
||||
expect(aim_probe_blocked() > 0)
|
||||
expect(not (aim_probe_disagree() > aim_probe_seen() / 50))
|
||||
}
|
||||
}
|
||||
15
packages/ludic.aim/use.ludic
Normal file
15
packages/ludic.aim/use.ludic
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# use.ludic - the thing the use key should act on. The ray comes first and proximity is its fallback,
|
||||
# never its override:
|
||||
# under the crosshair and in reach -> that, whatever else is nearer
|
||||
# under the crosshair and too far -> NOTHING (the prompt says why), within AIM_USE_MAX
|
||||
# nothing of that kind aimed at -> the nearest thing in reach
|
||||
# A player looking at something who presses the key has said which thing they mean.
|
||||
export const AIM_USE_THIS: int = 0
|
||||
export const AIM_USE_NOTHING: int = 1
|
||||
export const AIM_USE_NEAREST: int = 2
|
||||
|
||||
export function aim_use(kind: int) -> int {
|
||||
if am_kind == kind and am_reach { return AIM_USE_THIS }
|
||||
if am_kind == kind and am_dist < AIM_USE_MAX { return AIM_USE_NOTHING }
|
||||
return AIM_USE_NEAREST
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue