#include "../common/utils.lua" local TOOL_PICKAXE_ID = "pickaxe" local PI = 3.14159265358979323846 local PICKAXE_HIT_DIST = 3.0 local PICKAXE_HIT_RADIUS = 0.3 local HIT_THRESHOLD = 0.47 local SWING_UP = 0.2 local SWING_DOWN = 0.4 local REST_TRANSFORM = Transform(Vec(0.3, -0.7, -0.4), Quat(0.14, -0.19, -0.02, 0.96)) local SWING_START_TRANFORM = Transform(Vec(0.7, -0.4, -0.4), Quat(0.7, 0.11, -0.09, 0.7)) local SWING_END_TRANFORM = Transform(Vec(-0.3, -0.6, -0.4), Quat(-0.67, 0.08, -0.01, 0.73)) local HAPTIC_EFFECTS = { glass = "haptic/sledge_hit_glass.xml", wood = "haptic/sledge_hit_wood.xml", masonry = "haptic/sledge_hit_masonry.xml", plaster = "haptic/sledge_hit_masonry.xml", metal = "haptic/sledge_hit_metal.xml", dirt = "haptic/sledge_hit_dirt.xml" } function init() RegisterTool(TOOL_PICKAXE_ID, "loc@TOOL_NAME_PICKAXE", "MOD/data/vox/tool/pickaxe.vox", 1) SetBool("game.tool."..TOOL_PICKAXE_ID..".enabled", true) SetString("game.tool."..TOOL_PICKAXE_ID..".ammo.display", "") pickaxeStroke = 0.0 pickaxeHit = false end function tick(dt) local cameraTransform = GetCameraTransform() local toolBody = GetToolBody() local toolTransform = GetBodyTransform(toolBody) local localToolTransform = TransformToLocalTransform(cameraTransform, toolTransform) if GetString("game.player.tool") ~= TOOL_PICKAXE_ID then return end if InputPressed("usetool") and (pickaxeStroke == 0.0 or pickaxeStroke > 0.67)then pickaxeStroke = 0.01 pickaxeHit = false ShakeCamera(0.5) end if pickaxeStroke > 0 then local oldStroke = pickaxeStroke local strokeSpeed = pickaxeHit and 1.2 or 1.9 pickaxeStroke = pickaxeStroke + strokeSpeed * dt if pickaxeStroke >= HIT_THRESHOLD and oldStroke < HIT_THRESHOLD then local eyeTranform = GetPlayerCameraTransform() local castPos = TransformToParentPoint(eyeTranform, Vec()) local castFwd = TransformToParentVec(eyeTranform, Vec(0, 0, -1)) local hit, dist, normal, shape = QueryRaycast(castPos, castFwd, PICKAXE_HIT_DIST, PICKAXE_HIT_RADIUS) if hit then ShakeCamera(0.7) local hitPos = VecAdd(castPos, VecScale(castFwd, dist + 0.5)) local material, r, g, b, a = GetShapeMaterialAtPosition(shape, hitPos) local brokenCount = MakeHole(hitPos, 1) CreateImpactEffect(brokenCount > 0, { r = r, g = g, b = b, a = a }, hitPos, 0.4, normal) PlayHapticEffect(material) ApplyImpulse(hitPos) pickaxeHit = true else pickaxeHit = false end end if pickaxeStroke > 1.0 then pickaxeStroke = 0.0 end end local swing = Clamp(pickaxeStroke, 0.0, 1.0) local offset = Vec() if swing < SWING_UP then swing = swing / SWING_UP; swing = swing ^ 0.5 offset = TransformMix(REST_TRANSFORM, SWING_START_TRANFORM, swing); elseif pickaxeHit and swing > HIT_THRESHOLD then local swing2 = (HIT_THRESHOLD - SWING_UP) / (1.0 - SWING_UP - SWING_DOWN); swing2 = swing2 ^ 2.0 hitTransform = TransformMix(SWING_START_TRANFORM, SWING_END_TRANFORM, swing2); swing = (swing - HIT_THRESHOLD) / (1.0 - HIT_THRESHOLD); swing = InterpolateCosine(0.0, 1.0, swing); offset = TransformMix(hitTransform, REST_TRANSFORM, swing); elseif swing > SWING_UP and swing < (1.0 - SWING_DOWN) then swing = (swing - SWING_UP) / (1.0 - SWING_UP - SWING_DOWN) swing = swing ^ 2.0 offset = TransformMix(SWING_START_TRANFORM, SWING_END_TRANFORM, swing) elseif swing > SWING_DOWN then swing = (swing - SWING_DOWN) / (1.0 - SWING_DOWN); swing = InterpolateCosine(0.0, 1.0, swing) offset = TransformMix(SWING_END_TRANFORM, REST_TRANSFORM, swing) else offset = REST_TRANSFORM end SetToolTransform(offset) end function TransformMix(left, right, t) local pos = VecAdd(VecScale(left.pos, 1.0 - t), VecScale(right.pos, t)) local quat = QuatSlerp(left.rot, right.rot, t) return Transform(pos, quat) end function InterpolateCosine(left, right, t) return InterpolateLinear(left, right, (1.0 - math.cos(t * PI)) * 0.5) end function InterpolateLinear(left, right, t) return left * (1.0 - t) + right * t; end function CreateImpactEffect(broke, color, pos, size, normal, material) if broke then ParticleType("smoke") ParticleColor(color.r, color.g, color.b) ParticleAlpha(color.a) ParticleTile(6) ParticleCollide(1.0) ParticleGravity(-10) ParticleSticky(0.1) ParticleStretch(0.0) for i = 1, 80 do local r = rnd(0.0, 1.0) ^ 2 ParticleRadius(0.01 + r * 0.05, 0.001); local direction = RandomSeedVec(1.0) local spawnPos = VecAdd(VecAdd(pos, VecScale(direction, rnd(0.0, size))), VecScale(normal, 0.05)) SpawnParticle(spawnPos, GetParticleVelocity(direction), rnd(1.0, 2.0)) end ParticleReset() ParticleType("smoke") local mixed = MixColor({ r = 0.8, g = 0.85, b = 0.9 }, color, 0.5) ParticleColor(mixed.r, mixed.g, mixed.b) ParticleAlpha(0.4, 0.0); for i = 1, 32 do ParticleGravity(rnd(-9.0, 0.0)) ParticleRadius(0.1, 0.3) local direction = RandomSeedVec(1.0) local spawnPos = VecAdd(VecAdd(pos, VecScale(direction, rnd(0.0, size))), VecScale(normal, 0.05)) SpawnParticle(spawnPos, GetParticleVelocity(direction), rnd(0.8, 1.4)) end end if not broke or material == "hardmetal" or material == "heavymetal" or material == "rock" or material == "hardmasonry" then ParticleReset() ParticleType("smoke") ParticleColor(1.0, 0.3, 0.2) ParticleAlpha(1.0, 0.0) ParticleEmissive(10.0, 0.0) ParticleTile(4) ParticleCollide(1) ParticleGravity(-10) ParticleSticky(0.1) ParticleStretch(1.0) for i = 1, 24 do ParticleRadius(0.01, 0.001) local spread = RandomSeedVec(0.1) spread = VecSub(spread, VecScale(normal, VecDot(normal, spread))) local spawnPos = VecAdd(VecAdd(pos, spread), VecScale(normal, 0.05)) local baseVel = VecNormalize(VecSub(dir, VecScale(normal, VecScale(VecDot(normal, dir), 2.0)))) local s = rnd(0.0, 1.0) s = s ^ 2 local vel = VecNormalize(VecAdd(baseVel, VecScale(RandomSeedVec(0.8), s * 8.0))) SpawnParticle(spawnPos, vel, rnd(1.0, 2.0)) end ParticleReset() ParticleType("smoke") local mixed = MixColor({ r = 0.8, g = 0.85, b = 0.9 }, color, 0.5) ParticleColor(mixed.r, mixed.g, mixed.b) ParticleAlpha(0.5, 0.0); for i = 1, 32 do ParticleRadius(0.1, 0.3) local spread = RandomSeedVec(size) spread = VecSub(spread, VecScale(normal, VecDot(normal, spread))) local spawnPos = VecAdd(VecAdd(pos, spread), VecScale(normal, 0.05)) local baseVel = VecNormalize(VecSub(dir, VecScale(normal, VecScale(VecDot(normal, dir), 2.0)))) local s = rnd(0.0, 1.0) s = s ^ 2 local vel = VecNormalize(VecAdd(baseVel, VecScale(RandomSeedVec(0.7), s * 3.0))) SpawnParticle(spawnPos, vel, rnd(0.5, 1.0)) end end if material == "foliage" or material == "dirt" or material == "masonry" or material == "wood" or material == "plaster" then ParticleReset() ParticleType("smoke") local mixed = MixColor({ r = 0.8, g = 0.85, b = 0.9 }, color, 0.5) ParticleColor(mixed.r, mixed.g, mixed.b) ParticleAlpha(0.5, 0.0); for i = 1, 32 do ParticleRadius(0.1, 0.3) local spread = RandomSeedVec(size) spread = VecSub(spread, VecScale(normal, VecDot(normal, spread))) local spawnPos = VecAdd(VecAdd(pos, spread), VecScale(normal, 0.05)) local baseVel = VecNormalize(VecSub(dir, VecScale(normal, VecScale(VecDot(normal, dir), 2.0)))) local s = rnd(0.0, 1.0) ^ 2 local vel = VecNormalize(VecAdd(baseVel, VecScale(RandomSeedVec(0.7), s * 3.0))) SpawnParticle(spawnPos, vel, rnd(1.0, 2.0)) end end end function PlayHapticEffect(material) local effect = HAPTIC_EFFECTS[material] if effect then PlayHaptic(effect, 1.0) end end function ApplyImpulse(hitPos) local radius = EvenVec(1.0) local bodies = QueryAabbBodies(VecSub(hitPos, radius), VecAdd(hitPos, radius)) local playerTransform = GetPlayerTransform() local impulse = VecScale(VecNormalize(VecSub(hitPos, playerTransform.pos)), 3.0) for i = 1, #bodies do ApplyBodyImpulse(bodies[i], hitPos, impulse) end end function MixColor(left, right, t) return { r = InterpolateLinear(left.r, right.r, t), g = InterpolateLinear(left.g, right.g, t), b = InterpolateLinear(left.b, right.b, t) } end function GetParticleVelocity(dir) local s = rnd(0.0, 1.0) s = s ^ 2 return VecScale(direction, s * 2.5) end