#include "../common/utils.lua" #include "../game.lua" local EXPLOSIVE_TYPE_SHAPE = GetShapeWorldTransform local EXPLOSIVE_TYPE_BODY = GetBodyTransform local spawnFireTimer = 0 local spawnFireTimerMax = 0.15 local napalmRadius = 0.1 local powderLifetime = 0.6 local igniteMaxDist = 0.5 local stackDistThreshhold = 1 local coolDown = 0 local smoke = 0 local particleTimer = 0 local coolDownMax = 0.5 local distancePart = 0 local hitPos function GetTableLenght(table) local lenght = 0 for _, _ in pairs(table) do lenght = lenght + 1 end return lenght end function SpawnFireEffect(startPos) local particleVelocity = TransformToParentVec(GetCameraTransform(), Vec(rnd(-0.05, 0.05), 0.2, rnd(-1.5, -3))) ParticleReset() ParticleType("smoke") ParticleColor(1.0, 0.65, 0.5, 0.85, 0.33, 0.0) ParticleRadius(napalmRadius, napalmRadius * 2, "linear") ParticleTile(5) ParticleEmissive(3.0, 0.5, "easein") ParticleRotation(3, 0, "linear") ParticleStretch(8) ParticleCollide(1.0) SpawnParticle(startPos, particleVelocity, 0.5) end function init() RegisterTool("powder", "loc@TOOL_NAME_Powder", "MOD/data/vox/tool/explosive.vox", 1) local ammoScale = 1 if GetFloat("options.game.campaign.ammo") == -1 then ammoScale = 0 else ammoScale = ammoScale + GetFloat("options.game.campaign.ammo")/100 end for id, tool in pairs(gTools) do if id == "powder" then for j = 1, #tool.upgrades do local ammo = tool.upgrades[j].max if tool.upgrades[j].id == "ammo" then if ammoScale ~= 1 then ammo = math.floor(ammo * ammoScale) end SetInt("game.tool."..id..".ammo.max", ammo) SetInt("game.tool."..id..".ammo", ammo) break end end break end end SetString("game.tool.powder.ammo.display", string.format("%.1f", GetFloat("game.tool.powder.ammo"))) spawntime = 0 powderShapes = {} burningPowder = {} ignitionPoints = {} explosive = {} UpdateExplosivesList() lastSpawned = 0 soundBurning = LoadLoop("MOD/sound/gunpowder-burn.ogg") soundScattering = LoadLoop("MOD/sound/gunpowder-pour.ogg") end function UpdateExplosivesList() local explosiveBodies = FindBodies("explosive", true) for _, body in pairs(explosiveBodies) do if not explosive[body] then explosive[body] = EXPLOSIVE_TYPE_BODY end end local explosiveShapes = FindShapes("explosive", true) for _, shape in pairs(explosiveShapes) do if not explosive[shape] then explosive[shape] = EXPLOSIVE_TYPE_SHAPE end end end function SpawnPowder(pos) local powder = Spawn('', Transform(pos, QuatEuler(0, math.random(0, 180), 0)), true, true)[1] local min, max = GetShapeBounds(powder) powderShapes[powder] = VecLerp(min, max, 0.5) local topCenter = VecLerp(Vec(min[1], max[2], min[3]), max, 0.5) PlayLoop(soundScattering, topCenter, 10) return powder, topCenter end function tick(dt) if GetString("game.player.tool") == "powder" then local currentAmmo = GetFloat("game.tool.powder.ammo") if GetBool("game.player.canusetool") and InputDown("usetool") and currentAmmo > 0 then spawntime = spawntime + dt local cameraTransform = GetCameraTransform() if spawntime > 0.02 then currentAmmo = math.max(currentAmmo - dt, 0) QueryRequire("large") for shape, _ in pairs(powderShapes) do QueryRejectShape(shape) end local dir = TransformToParentVec(cameraTransform, Vec(0, 0, -1)) local hit, dist, normal, shape = QueryRaycast(cameraTransform.pos, dir, 3) if hit and not HasTag(shape, "horse") then distanceParticle = dist hitPos = VecAdd(cameraTransform.pos, VecScale(dir, dist)) if normal[2] > 0.9 then spawntime = 0 local powder, topCenter = SpawnPowder(hitPos) local newPowder = powder if IsHandleValid(lastSpawned) and GetPointsDistance(powderShapes[lastSpawned], powderShapes[powder]) < stackDistThreshhold then SetFloat("game.tool.powder.ammo", currentAmmo) SetString("game.tool.powder.ammo.display", string.format("%.1f", currentAmmo)) local currentPowderCenter = powderShapes[newPowder] if GetPointsDistance(powderShapes[lastSpawned], currentPowderCenter) > igniteMaxDist then local lastSpawnedPos = GetShapeWorldTransform(lastSpawned).pos local newSpawnedPos = GetShapeWorldTransform(newPowder).pos if lastSpawnedPos[2] - newSpawnedPos[2] > 0 then SpawnPowder(topCenter) else local min, max = GetShapeBounds(lastSpawned) SpawnPowder(VecLerp(Vec(min[1], max[2], min[3]), max, 0.5)) end end end lastSpawned = powder smokePos = TransformToParentPoint(cameraTransform, Vec(0, 0, -2)) smoke = math.min(1.0, smoke + 0.1) coolDown = coolDownMax end end end end coolDown = coolDown - dt local offset = Transform(Vec(0.5, -0.5, -1)) local recoil = math.max(0, coolDown) if recoil > 0 then offset.rot[1] = offset.rot[1] - recoil recoil = recoil - dt end SetToolTransform(offset) if smoke > 0 then if particleTimer < 0.0 then particleTimer = dt + (1.0 - smoke) * 0.05 local vel = VecScale(VecNormalize(VecSub(smokePos, hitPos)), -distanceParticle) ParticleReset() ParticleType("smoke") ParticleRadius(0.15, 0.25) ParticleAlpha(smoke * 10, 0) ParticleGravity(-2) ParticleDrag(1) ParticleColor(0.27, 0.16, 0.13, 0.85, 0.33, 0.0) SpawnParticle(smokePos, vel, 1) end end particleTimer = particleTimer - dt * 10 end smoke = math.max(0.0, smoke - dt * 5) local explosivePosTable = {} for expBody, expFunc in pairs(explosive) do explosivePosTable[expBody] = expFunc(expBody).pos end local cleenUpList = {} for index, powder in pairs(burningPowder) do if powder.lifeTime > 0 then powder.lifeTime = powder.lifeTime - dt SpawnFireEffect(powder.pos) Paint(powder.pos, 0.25, "explosion") PlayLoop(soundBurning, powder.pos, 3) for expBody, expPos in pairs(explosivePosTable) do if GetPointsDistance(powder.pos, expPos) <= 2 then Explosion(expPos, 0.5) explosive[expBody] = nil explosivePosTable[expBody] = nil end end else cleenUpList[index] = powder end end for index, powder in pairs(cleenUpList) do Delete(index) burningPowder[index] = nil end if spawnFireTimer < spawnFireTimerMax then spawnFireTimer = spawnFireTimer + dt end local toDeletepowderShapes = {} if spawnFireTimer > spawnFireTimerMax then for powderId, powderPos in pairs(powderShapes) do if QueryClosestFire(powderPos, igniteMaxDist) or (GetString("game.player.tool") ~= "sledge" and GetPointsDistance(powderPos, Vec(GetFloat("game.break.x"), GetFloat("game.break.y"), GetFloat("game.break.z"))) <= igniteMaxDist) then toDeletepowderShapes[powderId] = true burningPowder[powderId] = { pos = powderPos, lifeTime = powderLifetime } ignitionPoints[powderId] = powderPos end end local toDeleteIgnitions = {} for igniteId, ignitePos in pairs(ignitionPoints) do local newPointsFound = false for powderId, powderPos in pairs(powderShapes) do if GetPointsDistance(powderPos, ignitePos) <= igniteMaxDist then toDeletepowderShapes[powderId] = true burningPowder[powderId] = { pos = powderPos, lifeTime = powderLifetime } ignitionPoints[powderId] = powderPos newPointsFound = true end end toDeleteIgnitions[#toDeleteIgnitions + 1] = igniteId end for i = 1, #toDeleteIgnitions do ignitionPoints[toDeleteIgnitions[i]] = nil end if GetTableLenght(toDeletepowderShapes) > 0 then spawnFireTimer = 0 end end if lastSpawned ~= 0 and not powderShapes[lastSpawned] then lastSpawned = 0 end for powderId, powderPos in pairs(powderShapes) do if IsShapeBroken(powderId) then toDeletepowderShapes[powderId] = true end end for id, _ in pairs(toDeletepowderShapes) do Delete(id) powderShapes[id] = nil end end function update() UpdateExplosivesList() end function GetPointsDistance(left, right) return math.sqrt((left[1] - right[1]) ^ 2 + (left[2] - right[2]) ^ 2 + (left[3] - right[3]) ^ 2) end function handleCommand(command) if command == "quickload" then UpdateExplosivesList() end end