diff --git a/assets/cubyz/blocks/chain.zig.zon b/assets/cubyz/blocks/chain.zig.zon new file mode 100644 index 0000000000..f978ec9a25 --- /dev/null +++ b/assets/cubyz/blocks/chain.zig.zon @@ -0,0 +1,17 @@ +.{ + .tags = .{.mineable}, + .blockHealth = 2.5, + .drops = .{ + .{.items = .{.auto}}, + }, + .item = .{ + .texture = "chain.png", + }, + .rotation = "cubyz:direction", + .model = "cubyz:cross", + .transparent = true, + .collide = false, + .climbable = true, + .climbingSpeed = 170, + .texture = "cubyz:chain", +} diff --git a/assets/cubyz/blocks/rope.zig.zon b/assets/cubyz/blocks/rope.zig.zon new file mode 100644 index 0000000000..fd5279bbd5 --- /dev/null +++ b/assets/cubyz/blocks/rope.zig.zon @@ -0,0 +1,17 @@ +.{ + .tags = .{.mineable}, + .blockHealth = 2.5, + .item = .{ + .texture = "rope.png", + }, + .drops = .{ + .{.items = .{.auto}}, + }, + .rotation = "cubyz:direction", + .model = "cubyz:cross", + .transparent = true, + .collide = false, + .climbable = true, + .climbingSpeed = 100, + .texture = "cubyz:rope", +} diff --git a/assets/cubyz/blocks/textures/chain.png b/assets/cubyz/blocks/textures/chain.png new file mode 100644 index 0000000000..b907bd7ab9 Binary files /dev/null and b/assets/cubyz/blocks/textures/chain.png differ diff --git a/assets/cubyz/blocks/textures/rope.png b/assets/cubyz/blocks/textures/rope.png new file mode 100644 index 0000000000..76e1ece2e0 Binary files /dev/null and b/assets/cubyz/blocks/textures/rope.png differ diff --git a/assets/cubyz/items/textures/chain.png b/assets/cubyz/items/textures/chain.png new file mode 100644 index 0000000000..239ca009a5 Binary files /dev/null and b/assets/cubyz/items/textures/chain.png differ diff --git a/assets/cubyz/items/textures/rope.png b/assets/cubyz/items/textures/rope.png new file mode 100644 index 0000000000..8f00db371f Binary files /dev/null and b/assets/cubyz/items/textures/rope.png differ diff --git a/src/blocks.zig b/src/blocks.zig index ded98dba7d..6618cee3da 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -85,6 +85,8 @@ var _bounciness: [maxBlockCount]f32 = undefined; var _density: [maxBlockCount]f32 = undefined; var _terminalVelocity: [maxBlockCount]f32 = undefined; var _mobility: [maxBlockCount]f32 = undefined; +var _climbable: [maxBlockCount]bool = undefined; +var _climbingSpeed: [maxBlockCount]f32 = undefined; var _allowOres: [maxBlockCount]bool = undefined; var _onTick: [maxBlockCount]ServerBlockCallback = undefined; @@ -134,7 +136,8 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 { _terminalVelocity[size] = zon.get(f32, "terminalVelocity", 90); _mobility[size] = zon.get(f32, "mobility", 1.0); _allowOres[size] = zon.get(bool, "allowOres", false); - + _climbable[size] = zon.get(bool, "climbable", false); + _climbingSpeed[size] = zon.get(f32, "climbingSpeed", 1.0); _blockEntity[size] = block_entity.getByID(zon.get(?[]const u8, "blockEntity", null)); const oreProperties = zon.getChild("ore"); @@ -437,6 +440,14 @@ pub const Block = packed struct { // MARK: Block return _opaqueVariant[self.typ]; } + pub inline fn climbable(self: Block) bool { + return _climbable[self.typ]; + } + + pub inline fn climbingSpeed(self: Block) f32 { + return _climbingSpeed[self.typ]; + } + pub inline fn friction(self: Block) f32 { return _friction[self.typ]; } diff --git a/src/game.zig b/src/game.zig index e7ac75b25e..f6d4ee1ff3 100644 --- a/src/game.zig +++ b/src/game.zig @@ -224,12 +224,7 @@ pub const collision = struct { }; } - const VolumeProperties = struct { - terminalVelocity: f64, - density: f64, - maxDensity: f64, - mobility: f64, - }; + const VolumeProperties = struct {terminalVelocity: f64, density: f64, maxDensity: f64, mobility: f64, climbable: bool, climbingSpeed: f32}; fn overlapVolume(a: Box, b: Box) f64 { const min = @max(a.min, b.min); @@ -255,6 +250,8 @@ pub const collision = struct { var maxDensity: f64 = defaults.maxDensity; var mobilitySum: f64 = 0; var volumeSum: f64 = 0; + var climbable: bool = false; + var climbingSpeed: f32 = 1; var x: i32 = minX; while(x <= maxX) : (x += 1) { @@ -284,10 +281,14 @@ pub const collision = struct { densitySum += filledVolume*block.density(); maxDensity = @max(maxDensity, block.density()); mobilitySum += filledVolume*block.mobility(); + climbable = climbable or block.climbable(); + climbingSpeed = @max(block.climbingSpeed(), climbingSpeed); } else { invTerminalVelocitySum += gridVolume/defaults.terminalVelocity; densitySum += gridVolume*defaults.density; mobilitySum += gridVolume*defaults.mobility; + climbable = defaults.climbable; + climbingSpeed = defaults.climbingSpeed; } } } @@ -298,6 +299,8 @@ pub const collision = struct { .density = densitySum/volumeSum, .maxDensity = maxDensity, .mobility = mobilitySum/volumeSum, + .climbable = climbable, + .climbingSpeed = climbingSpeed, }; } @@ -437,7 +440,7 @@ pub const Player = struct { // MARK: Player pub var selectionPosition2: ?Vec3i = null; pub var currentFriction: f32 = 0; - pub var volumeProperties: collision.VolumeProperties = .{.density = 0, .maxDensity = 0, .mobility = 0, .terminalVelocity = 0}; + pub var volumeProperties: collision.VolumeProperties = .{.density = 0, .maxDensity = 0, .mobility = 0, .terminalVelocity = 0, .climbable = false, .climbingSpeed = 1.0}; pub var onGround: bool = false; pub var jumpCooldown: f64 = 0; @@ -824,6 +827,8 @@ pub fn update(deltaTime: f64) void { // MARK: update() const mobility = if(Player.isFlying.load(.monotonic)) 1.0 else Player.volumeProperties.mobility; const density = if(Player.isFlying.load(.monotonic)) 0.0 else Player.volumeProperties.density; const maxDensity = if(Player.isFlying.load(.monotonic)) 0.0 else Player.volumeProperties.maxDensity; + const isClimbing = if(Player.isFlying.load(.monotonic)) false else Player.volumeProperties.climbable; + const climbingSpeed = if(Player.isFlying.load(.monotonic)) 1.0 else Player.volumeProperties.climbingSpeed; const baseFrictionCoefficient: f32 = Player.currentFriction; var jumping = false; @@ -839,80 +844,110 @@ pub fn update(deltaTime: f64) void { // MARK: update() if(main.Window.grabbed) { const walkingSpeed: f64 = if(Player.crouching) 2.5 else 4.5; - if(KeyBoard.key("forward").value > 0.0) { - if(KeyBoard.key("sprint").pressed and !Player.crouching) { - if(Player.isGhost.load(.monotonic)) { - movementSpeed = @max(movementSpeed, 128)*KeyBoard.key("forward").value; - movementDir += forward*@as(Vec3d, @splat(128*KeyBoard.key("forward").value)); - } else if(Player.isFlying.load(.monotonic)) { - movementSpeed = @max(movementSpeed, 32)*KeyBoard.key("forward").value; - movementDir += forward*@as(Vec3d, @splat(32*KeyBoard.key("forward").value)); - } else { - movementSpeed = @max(movementSpeed, 8)*KeyBoard.key("forward").value; - movementDir += forward*@as(Vec3d, @splat(8*KeyBoard.key("forward").value)); - } - } else { - movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("forward").value; - movementDir += forward*@as(Vec3d, @splat(walkingSpeed*KeyBoard.key("forward").value)); + const horizontalClimbSpeed = 8; + if(isClimbing and !Player.onGround) { + // Climbing Controls + if(KeyBoard.key("forward").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed*horizontalClimbSpeed)*KeyBoard.key("forward").value; + movementDir += forward*@as(Vec3d, @splat(walkingSpeed*horizontalClimbSpeed*KeyBoard.key("forward").value)); } - } - if(KeyBoard.key("backward").value > 0.0) { - movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("backward").value; - movementDir += forward*@as(Vec3d, @splat(-walkingSpeed*KeyBoard.key("backward").value)); - } - if(KeyBoard.key("left").value > 0.0) { - movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("left").value; - movementDir += right*@as(Vec3d, @splat(walkingSpeed*KeyBoard.key("left").value)); - } - if(KeyBoard.key("right").value > 0.0) { - movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("right").value; - movementDir += right*@as(Vec3d, @splat(-walkingSpeed*KeyBoard.key("right").value)); - } - if(KeyBoard.key("jump").pressed) { - if(Player.isFlying.load(.monotonic)) { - if(KeyBoard.key("sprint").pressed) { + if(KeyBoard.key("backward").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed*horizontalClimbSpeed)*KeyBoard.key("backward").value; + movementDir += forward*@as(Vec3d, @splat(-walkingSpeed*horizontalClimbSpeed*KeyBoard.key("backward").value)); + } + if(KeyBoard.key("left").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed*horizontalClimbSpeed)*KeyBoard.key("left").value; + movementDir += right*@as(Vec3d, @splat(walkingSpeed*horizontalClimbSpeed*KeyBoard.key("left").value)); + } + if(KeyBoard.key("right").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed*horizontalClimbSpeed)*KeyBoard.key("right").value; + movementDir += right*@as(Vec3d, @splat(-walkingSpeed*horizontalClimbSpeed*KeyBoard.key("right").value)); + } + if(KeyBoard.key("jump").pressed) { + movementSpeed = @max(movementSpeed, climbingSpeed); + movementDir[2] += climbingSpeed; + } + if(KeyBoard.key("fall").pressed) { + movementSpeed = @max(movementSpeed, climbingSpeed); + movementDir[2] -= climbingSpeed; + } + } else { + // Normal Controls + if(KeyBoard.key("forward").value > 0.0) { + if(KeyBoard.key("sprint").pressed and !Player.crouching) { if(Player.isGhost.load(.monotonic)) { - movementSpeed = @max(movementSpeed, 60); - movementDir[2] += 60; + movementSpeed = @max(movementSpeed, 128)*KeyBoard.key("forward").value; + movementDir += forward*@as(Vec3d, @splat(128*KeyBoard.key("forward").value)); + } else if(Player.isFlying.load(.monotonic)) { + movementSpeed = @max(movementSpeed, 32)*KeyBoard.key("forward").value; + movementDir += forward*@as(Vec3d, @splat(32*KeyBoard.key("forward").value)); } else { - movementSpeed = @max(movementSpeed, 25); - movementDir[2] += 25; + movementSpeed = @max(movementSpeed, 8)*KeyBoard.key("forward").value; + movementDir += forward*@as(Vec3d, @splat(8*KeyBoard.key("forward").value)); } } else { - movementSpeed = @max(movementSpeed, 5.5); - movementDir[2] += 5.5; + movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("forward").value; + movementDir += forward*@as(Vec3d, @splat(walkingSpeed*KeyBoard.key("forward").value)); } - } else if((Player.onGround or Player.jumpCoyote > 0.0) and Player.jumpCooldown <= 0) { - jumping = true; - Player.jumpCooldown = Player.jumpCooldownConstant; - if(!Player.onGround) { - Player.eye.coyote = 0; + } + if(KeyBoard.key("backward").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("backward").value; + movementDir += forward*@as(Vec3d, @splat(-walkingSpeed*KeyBoard.key("backward").value)); + } + if(KeyBoard.key("left").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("left").value; + movementDir += right*@as(Vec3d, @splat(walkingSpeed*KeyBoard.key("left").value)); + } + if(KeyBoard.key("right").value > 0.0) { + movementSpeed = @max(movementSpeed, walkingSpeed)*KeyBoard.key("right").value; + movementDir += right*@as(Vec3d, @splat(-walkingSpeed*KeyBoard.key("right").value)); + } + if(KeyBoard.key("jump").pressed) { + if(Player.isFlying.load(.monotonic)) { + if(KeyBoard.key("sprint").pressed) { + if(Player.isGhost.load(.monotonic)) { + movementSpeed = @max(movementSpeed, 60); + movementDir[2] += 60; + } else { + movementSpeed = @max(movementSpeed, 25); + movementDir[2] += 25; + } + } else { + movementSpeed = @max(movementSpeed, 5.5); + movementDir[2] += 5.5; + } + } else if((Player.onGround or Player.jumpCoyote > 0.0) and Player.jumpCooldown <= 0) { + jumping = true; + Player.jumpCooldown = Player.jumpCooldownConstant; + if(!Player.onGround) { + Player.eye.coyote = 0; + } + Player.jumpCoyote = 0; + } else if(!KeyBoard.key("fall").pressed) { + movementSpeed = @max(movementSpeed, walkingSpeed); + movementDir[2] += walkingSpeed; } - Player.jumpCoyote = 0; - } else if(!KeyBoard.key("fall").pressed) { - movementSpeed = @max(movementSpeed, walkingSpeed); - movementDir[2] += walkingSpeed; + } else { + Player.jumpCooldown = 0; } - } else { - Player.jumpCooldown = 0; - } - if(KeyBoard.key("fall").pressed) { - if(Player.isFlying.load(.monotonic)) { - if(KeyBoard.key("sprint").pressed) { - if(Player.isGhost.load(.monotonic)) { - movementSpeed = @max(movementSpeed, 60); - movementDir[2] -= 60; + if(KeyBoard.key("fall").pressed) { + if(Player.isFlying.load(.monotonic)) { + if(KeyBoard.key("sprint").pressed) { + if(Player.isGhost.load(.monotonic)) { + movementSpeed = @max(movementSpeed, 60); + movementDir[2] -= 60; + } else { + movementSpeed = @max(movementSpeed, 25); + movementDir[2] -= 25; + } } else { - movementSpeed = @max(movementSpeed, 25); - movementDir[2] -= 25; + movementSpeed = @max(movementSpeed, 5.5); + movementDir[2] -= 5.5; } - } else { - movementSpeed = @max(movementSpeed, 5.5); - movementDir[2] -= 5.5; + } else if(!KeyBoard.key("jump").pressed) { + movementSpeed = @max(movementSpeed, walkingSpeed); + movementDir[2] -= walkingSpeed; } - } else if(!KeyBoard.key("jump").pressed) { - movementSpeed = @max(movementSpeed, walkingSpeed); - movementDir[2] -= walkingSpeed; } } diff --git a/src/physics.zig b/src/physics.zig index b9aa7db27e..4fb8a35dcf 100644 --- a/src/physics.zig +++ b/src/physics.zig @@ -18,7 +18,7 @@ const playerDensity = 1.2; pub fn calculateProperties() void { if(main.renderer.mesh_storage.getBlockFromRenderThread(@intFromFloat(@floor(Player.super.pos[0])), @intFromFloat(@floor(Player.super.pos[1])), @intFromFloat(@floor(Player.super.pos[2]))) != null) { - Player.volumeProperties = collision.calculateVolumeProperties(.client, Player.super.pos, Player.outerBoundingBox, .{.density = 0.001, .terminalVelocity = airTerminalVelocity, .maxDensity = 0.001, .mobility = 1.0}); + Player.volumeProperties = collision.calculateVolumeProperties(.client, Player.super.pos, Player.outerBoundingBox, .{.density = 0.001, .terminalVelocity = airTerminalVelocity, .maxDensity = 0.001, .mobility = 1.0, .climbable = false, .climbingSpeed = 1.0}); const groundFriction = if(!Player.onGround and !Player.isFlying.load(.monotonic)) 0 else collision.calculateSurfaceProperties(.client, Player.super.pos, Player.outerBoundingBox, 20).friction; const volumeFrictionCoeffecient: f32 = @floatCast(gravity/Player.volumeProperties.terminalVelocity); @@ -31,13 +31,15 @@ pub fn update(deltaTime: f64, inputAcc: Vec3d, jumping: bool) void { // MARK: up if(main.renderer.mesh_storage.getBlockFromRenderThread(@intFromFloat(@floor(Player.super.pos[0])), @intFromFloat(@floor(Player.super.pos[1])), @intFromFloat(@floor(Player.super.pos[2]))) != null) { const effectiveGravity = gravity*(playerDensity - Player.volumeProperties.density)/playerDensity; const volumeFrictionCoeffecient: f32 = @floatCast(gravity/Player.volumeProperties.terminalVelocity); + const isClimbing = Player.volumeProperties.climbable and !Player.onGround; var acc = inputAcc; - if(!Player.isFlying.load(.monotonic)) { + if(!Player.isFlying.load(.monotonic) and !isClimbing) { acc[2] -= effectiveGravity; } const baseFrictionCoefficient: f32 = Player.currentFriction; var directionalFrictionCoefficients: Vec3f = @splat(0); + const climbingFriction = 10; // This our model for movement on a single frame: // dv/dt = a - λ·v @@ -51,6 +53,10 @@ pub fn update(deltaTime: f64, inputAcc: Vec3d, jumping: bool) void { // MARK: up Player.super.vel[i] = @max(jumpVelocity, Player.super.vel[i] + jumpVelocity); frictionCoefficient = volumeFrictionCoeffecient; } + + if(isClimbing and !Player.isFlying.load(.monotonic)) { // High friction while climbing + frictionCoefficient = climbingFriction; + } const v_0 = Player.super.vel[i]; const a = acc[i]; // Here the solution can be easily derived: