Skip to content

Commit

Permalink
Merge pull request #5 from creelonestudios/f-gravity-new
Browse files Browse the repository at this point in the history
Gravity
  • Loading branch information
j0code authored Feb 5, 2024
2 parents 1b511f1 + addd2f9 commit 5c512c4
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 23 deletions.
19 changes: 19 additions & 0 deletions src/defs/EntityDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,31 @@ import Base from "./Base.js"

export default class EntityDef extends Base {

readonly hasFriction: boolean

constructor(namespace: string, idname: string, data: any) {
super(namespace, idname)
if (!validate(data)) throw new Error(`Invalid entitydef for ${namespace}:${idname}: ${JSON.stringify(data)}`)

this.hasFriction = data.hasFriction
}

get assetsPath() {
return `${this.namespace}/textures/entity/${this.idname}.png`
}

}

type EntityDefData = {
hasFriction: boolean
}

function validate(data: any): data is EntityDefData {
if ("hasFriction" in data) {
if (typeof data.hasFriction != "boolean") return false
} else {
data.hasFriction = false
}

return true
}
4 changes: 4 additions & 0 deletions src/dim/Dim.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
export default interface Dim {

add(dim: Dim): Dim,
sub(dim: Dim): Dim,
mult(dim: Dim): Dim,
set(dim: Dim | number): Dim,
scale(x: number): Dim,
sqMag(): number
mag(): number,
normalize(): Dim,
floor(): Dim,
copy(): Dim

Expand Down
28 changes: 24 additions & 4 deletions src/dim/Dim2.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dim from "./Dim"
import Dim from "./Dim.js"
import Dim3 from "./Dim3.js"

export default class Dim2 implements Dim {

Expand All @@ -10,18 +11,24 @@ export default class Dim2 implements Dim {
this.y = y
}

add(dim: Dim2): Dim2 {
add(dim: Dim2 | Dim3): Dim2 {
this.x += dim.x
this.y += dim.y
return this
}

sub(dim: Dim2 | Dim3): Dim2 {
this.x -= dim.x
this.y -= dim.y
return this
}

mult(dim: Dim2): Dim2 {
return this
}

set(dim: Dim2 | number, y?: number): Dim2 {
if (dim instanceof Dim2) {
set(dim: Dim2 | Dim3 | number, y?: number): Dim2 {
if (dim instanceof Dim2 || dim instanceof Dim3) {
this.x = dim.x
this.y = dim.y
} else {
Expand All @@ -37,6 +44,19 @@ export default class Dim2 implements Dim {
return this
}

sqMag(): number {
return this.x ** 2 + this.y ** 2
}

mag(): number {
return Math.sqrt(this.sqMag())
}

normalize(): Dim2 {
const mag = this.mag()
return this.scale(1/mag)
}

floor(): Dim2 {
this.x = Math.floor(this.x)
this.y = Math.floor(this.y)
Expand Down
31 changes: 26 additions & 5 deletions src/dim/Dim3.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dim from "./Dim.js"
import Dim2 from "./Dim2.js"

export default class Dim3 implements Dim {

Expand All @@ -12,22 +13,29 @@ export default class Dim3 implements Dim {
this.z = z
}

add(dim: Dim3): Dim3 {
add(dim: Dim2 | Dim3): Dim3 {
this.x += dim.x
this.y += dim.y
this.z += dim.z
if (dim instanceof Dim3) this.z += dim.z
return this
}

sub(dim: Dim2 | Dim3): Dim3 {
this.x += dim.x
this.y += dim.y
if (dim instanceof Dim3) this.z += dim.z
return this
}

mult(dim: Dim3): Dim3 {
return this
}

set(dim: Dim3 | number, y?: number, z?: number): Dim3 {
if (dim instanceof Dim3) {
set(dim: Dim2 | Dim3 | number, y?: number, z?: number): Dim3 {
if (dim instanceof Dim2 || dim instanceof Dim3) {
this.x = dim.x
this.y = dim.y
this.z = dim.z
if (dim instanceof Dim3) this.z = dim.z
} else {
this.x = dim
this.y = y || 0
Expand All @@ -43,6 +51,19 @@ export default class Dim3 implements Dim {
return this
}

sqMag(): number {
return this.x ** 2 + this.y ** 2 + this.z ** 2
}

mag(): number {
return Math.sqrt(this.sqMag())
}

normalize(): Dim2 {
const mag = this.mag()
return this.scale(1/mag)
}

floor(): Dim3 {
this.x = Math.floor(this.x)
this.y = Math.floor(this.y)
Expand Down
83 changes: 81 additions & 2 deletions src/entity/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ import Graphics from "../Graphics.js"

export default class Entity {

static readonly TERMINAL_VELOCITY = 3
static readonly TERMINAL_FLUID_VELOCITY = 0.15
static readonly GRAVITY = 0.045

static applyGravity(motion: Dim3, inFluid: boolean) {
if (inFluid) {
motion.y -= Entity.GRAVITY * 0.5
if (motion.y < -Entity.TERMINAL_FLUID_VELOCITY) motion.y = -Entity.TERMINAL_FLUID_VELOCITY
} else {
motion.y -= Entity.GRAVITY
if (motion.y < -Entity.TERMINAL_VELOCITY) motion.y = -Entity.TERMINAL_VELOCITY
}
}

readonly def: EntityDef
readonly position: Dim3
readonly rotation: Dim3
Expand All @@ -16,6 +30,8 @@ export default class Entity {
readonly spawnTime: number

noGravity: boolean
onGround: boolean
inFluid: boolean

constructor(def: EntityDef | string, data: Partial<EntityData> = {}) {
if (def instanceof EntityDef) this.def = def
Expand All @@ -24,7 +40,7 @@ export default class Entity {
if (entitydef) this.def = entitydef
else {
console.trace()
throw "Block definition not found: " + def
throw "Entity definition not found: " + def
}
}
this.position = new Dim3(...(data.position || [0, 0, 0]))
Expand All @@ -33,6 +49,8 @@ export default class Entity {
this.size = new Dim2()
this.spawnTime = data.spawnTime || Date.now() // TODO: ticks since world creation
this.noGravity = typeof data.noGravity == "undefined" ? false : data.noGravity
this.onGround = typeof data.onGround == "undefined" ? false : data.onGround
this.inFluid = false
}

get id() {
Expand All @@ -58,7 +76,64 @@ export default class Entity {
}

tick(world: World) {
const {x: pX, y: pY, z: pZ} = this.position // p -> present
const box = this.getBoundingBox()
let onGround = false
let inFluid = false

// check fluid collision
loop: for (let y = Math.floor(box.pos.y); y <= Math.ceil(box.corner.y); y++) {
for (let x = Math.floor(box.pos.x); x <= Math.ceil(box.corner.x); x++) {
const block = world.getBlock(x, y, pZ)
if (block && block.type == "fluid") {
if (box.intersect(block?.getBoundingBox(x, y))) {
inFluid = true
break loop
}
}
}
}
//console.log("inFluid:", inFluid)

const motion = this.motion.copy()
Entity.applyGravity(motion, inFluid)
const fY = pY + motion.y // f -> future

// check ground collision (but not walls or ceiling)
loop: for (let y = Math.floor(pY) -1; y >= Math.floor(fY); y--) {
for (let x = Math.floor(box.pos.x); x <= Math.ceil(box.corner.x); x++) {
box.pos.y = y
const blockBelow = world.getBlock(x, y, pZ)
if (blockBelow && blockBelow.isSolid()) {
if (box.intersect(blockBelow?.getBoundingBox(x, y))) {
onGround = true
this.motion.y = 0
this.position.y = y + 1
break loop
}
}
}
}

if (onGround && this.motion.y < 0) this.motion.y = 0

// apply gravity
if (!onGround && !this.noGravity) {
Entity.applyGravity(this.motion, inFluid)
}

// ground friction
if ((onGround || inFluid) && this.def.hasFriction) {
this.motion.x *= 0.75
if (Math.abs(this.motion.x) <= 0.5 ** 5) this.motion.x = 0
}

this.onGround = onGround
this.inFluid = inFluid

this.position.add(this.motion)

if (this.position.y < world.minY - 50) this.die(world)
}

draw(g: Graphics) {
Expand All @@ -71,14 +146,18 @@ export default class Entity {
g.restore()
}

die(world: World) {
world.removeEntity(this)
}

getData(): EntityData {
return {
id: this.id,
motion: this.motion.asArray(),
position: this.position.asArray(), // TODO: "pos" alias for compatibility
rotation: this.rotation.asArray(),
noGravity: this.noGravity,
onGround: false,
onGround: this.onGround,
spawnTime: this.spawnTime
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/entity/ItemEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class ItemEntity extends Entity {
readonly itemstack: ItemStack

constructor(itemstack: ItemStack | null, data: Partial<ItemEntityData> = {}) {
super(new EntityDef("tiny", "item", {}), data)
super(new EntityDef("tiny", "item", { hasFriction: true }), data)
this.itemstack = data.item ? new ItemStack(data.item.item.id, data.item.amount) : (itemstack || new ItemStack("tiny:air"))
this.size.set(0.5, 0.5)
}
Expand All @@ -22,7 +22,9 @@ export default class ItemEntity extends Entity {
}

tick(world: World) {
if (this.inFluid) this.motion.y = Entity.TERMINAL_FLUID_VELOCITY
super.tick(world)

const pickupDelay = Math.max(this.spawnTime + ItemEntity.PICKUP_TIME - Date.now(), 0)
const boundingBox = this.getBoundingBox()

Expand Down Expand Up @@ -51,6 +53,7 @@ export default class ItemEntity extends Entity {
if (boundingBox.intersect(item.getBoundingBox())) {
if (stack.amount + this.itemstack.amount < this.itemstack.item.maxItemStack) {
stack.amount += this.itemstack.amount
item.motion.add(this.motion)
world.removeEntity(this)
return
}
Expand Down
6 changes: 6 additions & 0 deletions src/entity/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export default class Player extends Entity {
}
}

die() { // respawn
this.position.set(0, 1, 0) // TODO: spawn point
this.motion.set(0, 0, 0)
this.rotation.set(0, 0)
}

tick(world: World) {
super.tick(world)
}
Expand Down
37 changes: 26 additions & 11 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ export function getTexture(path: string) {
}

function tick() {
player.motion.x = (Number(input.pressed("KeyD")) - Number(input.pressed("KeyA"))) * 0.25
player.motion.y = (Number(input.pressed("KeyW")) - Number(input.pressed("KeyS"))) * 0.25
player.motion.x = (Number(input.pressed("KeyD")) - Number(input.pressed("KeyA"))) * 0.15
//player.motion.y = (Number(input.pressed("KeyW")) - Number(input.pressed("KeyS"))) * 0.25
if (player.inFluid && input.pressed("Space")) player.motion.y = Entity.TERMINAL_FLUID_VELOCITY
world.tick()
}

Expand Down Expand Up @@ -203,19 +204,29 @@ input.on("keydown", (key: string) => {
// temp
debug.showAirLightLevel = debug.showOrigin
}

if (key == "KeyQ") {
let stack = player.selectedItem
let index = player.selectedItemSlot
const stack = player.selectedItem
const index = player.selectedItemSlot
if (stack.item.id == "tiny:air") return
if (input.pressed("ControlLeft")) { // drop whole stack
world.spawn(new ItemEntity(stack, { position: player.position.asArray() }))
player.hotbar.set(index, new ItemStack("tiny:air"))
} else { // drop single item
world.spawn(new ItemEntity(new ItemStack(stack.item.id), { position: player.position.asArray() }))
if (stack.amount > 1) stack.amount--
else player.hotbar.set(index, new ItemStack("tiny:air"))

const entityData = {
position: player.position.asArray(),
motion: getMousePos().sub(player.position).normalize().scale(0.6).asArray()
}
let dropStack = stack

if (!input.pressed("ControlLeft")) {
dropStack = new ItemStack(stack.item.id)
}

if (input.pressed("ControlLeft") || stack.amount <= 1) {
player.hotbar.set(index, new ItemStack("tiny:air"))
} else stack.amount--

world.spawn(new ItemEntity(dropStack, entityData))
}

inv: if (key == "KeyE") { // open inventory under mouse
if (Container.showingInventory()) {
Container.setInventory()
Expand All @@ -235,6 +246,10 @@ input.on("keydown", (key: string) => {
debug.showDebugScreen = !debug.showDebugScreen
}

if (key == "Space") {
if (!player.inFluid && player.onGround) player.motion.y = 0.35
}

if (key == "F11" || key == "F1") {
if (document.fullscreenElement) document.exitFullscreen()
else game.requestFullscreen()
Expand Down

0 comments on commit 5c512c4

Please sign in to comment.