diff --git a/objects/wired/fu_turrets/fu_flameturret/emptyenergy.frames b/objects/wired/fu_turrets/fu_flameturret/emptyenergy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/emptyenergy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/emptyenergy.png b/objects/wired/fu_turrets/fu_flameturret/emptyenergy.png new file mode 100644 index 00000000000..bf2a5ba3113 Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/emptyenergy.png differ diff --git a/objects/wired/fu_turrets/fu_flameturret/energy.frames b/objects/wired/fu_turrets/fu_flameturret/energy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/energy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/energy.png b/objects/wired/fu_turrets/fu_flameturret/energy.png new file mode 100644 index 00000000000..2448d72a966 Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/energy.png differ diff --git a/objects/wired/fu_turrets/fu_flameturret/fu_flameturret.object b/objects/wired/fu_turrets/fu_flameturret/fu_flameturret.object new file mode 100644 index 00000000000..6d7390aa5ec --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/fu_flameturret.object @@ -0,0 +1,196 @@ +{ + "objectName" : "fu_flameturret", + "colonyTags" : ["wired","combat"], + "printable" : false, + "rarity" : "Common", + "objectType" : "container", + "price" : 150, + + "slotCount" : 0, + "uiConfig" : "/interface/turret/standingturret.config", + "frameCooldown" : 5, + "autoCloseCooldown" : 3600, + "health" : 5, + "description" : "Turret for your protection! Can only be placed once.", + "shortdescription" : "Flame Turret", + "subtitle" : "Automatic Base Defense", + "race" : "generic", + "category" : "wire", + + "breakDropOptions" : [ + [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ], [ "isn_flamethrower", 1, { } ] ] + ], + + "apexDescription" : "Always watchful.", + "avianDescription" : "The monsters don't stand a chance, unless they can fly.", + "floranDescription" : "Effective againssst living thingsss.", + "glitchDescription" : "Cautious. Must not disrupt my brother in his vigilance.", + "humanDescription" : "An automated security system.", + "hylotlDescription" : "Looks like a turret of some sort.", + "novakidDescription" : "Looks like this thing sure could do some damage!", + + "inventoryIcon" : "icon.png", + "orientations" : [ + { + "dualImage" : "turretstand.png:bottom", + + "imagePosition" : [-16, 0], + "imageLayers" : [ { "image" : "turretstand.png:bottom" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, 0], [-1, 0], [0, 0], [1, 0], + [-2, 1], [-1, 1], [0, 1], [1, 1] + ], + "anchors" : [ "bottom" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:bottom", + "energy" : "energy.png" + }, + "animationPosition" : [0, 16], + "animationCenterLine" : 0, + + "baseOffset" : [0, 2], + "energyBarOffset" : [0.375, -1.5], + "verticalScaling" : false + }, + { + "dualImage" : "turretstand.png:top", + + "imagePosition" : [-16, -24], + "imageLayers" : [ { "image" : "turretstand.png:top" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, -1], [-1, -1], [0, -1], [1, -1], + [-2, 0], [-1, 0], [0, 0], [1, 0] + ], + "anchors" : [ "top" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:top", + "energy" : "energy.png" + }, + "animationPosition" : [0, -8], + "animationCenterLine" : 0, + + "baseOffset" : [0, -1], + "energyBarOffset" : [0.375, 1.375], + "verticalScaling" : false + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [0, 0], + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [0, 0], [1, 0], + [0, 1], [1, 1], + [0, 2], [1, 2] + ], + "anchors" : [ "left" ], + "direction" : "right", + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [16, 16], + + "baseOffset" : [2, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [-24, 0], + "spaces" : [ + [-1, 0], [0, 0], + [-1, 1], [0, 1], + [-1, 2], [0, 2] + ], + "anchors" : [ "right" ], + "direction" : "left", + + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "flipImages" : true, + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [-10, 16], + + "baseOffset" : [-1, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + } + ], + + "objectWidth" : 4, + + "animation" : "standingturret.animation", + + "scripts" : [ + "/objects/wired/fu_turrets/fu_flameturret/standingturret.lua", + "/scripts/npcToyObject.lua", + "/scripts/stateMachine.lua", + "/scripts/util.lua", + "/scripts/vec2.lua" + ], + + "damageTeam" : { + "type" : "assistant" + }, + + "scriptDelta" : 5, + + "outputNodes" : [ [0, 0] ], + "inputNodes" : [ [0, 1] ], + + "rotationSpeed" : 25, + + "tipOffset" : [2.5, 0], + "offAngle" : -30, + + "scanInterval" : 6, + "scanAngle" : 30, + "scanRange" : 35, + + "targetQueryRange" : 35, + "targetMinRange" : 2.5, + "targetMaxRange" : 50, + "targetAngleRange" : 75, + + "maxFireAngle" : 5, + + "energyUsage" : 4, + "power" : 1, + "fireTime" : 0.1, + + "maxEnergy" : 100, + "energyRegen" : 25, + "energyRegenBlock" : 1.0, + + "npcToy" : { + "influence" : [ + "turret" + ], + "defaultReactions" : { + "turret" : [ + [1.0, "pressbutton"], + [1.0, "oh"], + [1.0, "laugh"] + ] + }, + "preciseStandPositionLeft" : [-1.0, 0.0], + "preciseStandPositionRight" : [1.0, 0.0], + "maxNpcs" : 1 + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/icon.png b/objects/wired/fu_turrets/fu_flameturret/icon.png new file mode 100644 index 00000000000..b7e30de972a Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/icon.png differ diff --git a/objects/wired/fu_turrets/fu_flameturret/standingturret.animation b/objects/wired/fu_turrets/fu_flameturret/standingturret.animation new file mode 100644 index 00000000000..f4c44bc7246 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/standingturret.animation @@ -0,0 +1,166 @@ +{ + "animatedParts" : { + "stateTypes" : { + "attack" : { + "priority" : 0, + "default" : "dead", + + "states" : { + "idle" : { + "frames" : 1 + }, + "dead" : { + "frames" : 1 + }, + "attack" : { + "frames" : 4, + "cycle" : 0.25, + "mode" : "loop" + } + } + }, + "energy" : { + "default" : "full", + + "states" : { + "full" : { "frames" : 1 }, + "high" : { "frames" : 1 }, + "medium" : { "frames" : 1 }, + "low" : { "frames" : 1 }, + "none" : { "frames" : 1 } + } + }, + "opened" : { + "default" : "true", + + "states" : { + "true" : { "frames" : 1 }, + "false" : { "frames" : 1 } + } + } + }, + + "parts" : { + "gun" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [2.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "fullbright" : true, + "zLevel" : 3 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "gunfullbright" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [2.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "zLevel" : 4 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "stand" : { + "properties" : { + "offset" : [0, 0], + "image" : "", + "centered" : true, + "zLevel" : 1 + } + }, + + "energy" : { + "properties" : { + "centered" : false, + "zLevel" : 2, + "transformationGroups" : [ "energy" ] + }, + + "partStates" : { + "energy" : { + "full" : { + "properties" : { "image" : ":full" } + }, + "high" : { + "properties" : { "image" : ":high" } + }, + "medium" : { + "properties" : { "image" : ":medium" } + }, + "low" : { + "properties" : { "image" : ":low" } + }, + "none" : { + "properties" : { "image" : ":none" } + } + } + } + } + } + }, + + "rotationGroups" : { + "gun" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + }, + "gunfullbright" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + } + }, + + "transformationGroups" : { + "energy" : { "interpolated" : false } + }, + + "sounds" : { + "powerUp" : ["/sfx/tech/mech_jump3.ogg"], + "powerDown" : ["/sfx/tech/mech_powerdown2.ogg"], + "foundTarget" : ["/sfx/interface/nav_computer_on.ogg"], + "scan" : ["/sfx/interface/scan.ogg"], + "fire" : [ "/items/active/weapons/ranged/unique/science/flamepistol/isn_flamethrower.wav" ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/standingturret.lua b/objects/wired/fu_turrets/fu_flameturret/standingturret.lua new file mode 100644 index 00000000000..80556a546b9 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/standingturret.lua @@ -0,0 +1,214 @@ +require "/scripts/util.lua" +require "/scripts/interp.lua" + +function init() + -- Positions and angles + self.baseOffset = config.getParameter("baseOffset") + self.basePosition = vec2.add(object.position(), self.baseOffset) + self.tipOffset = config.getParameter("tipOffset") --This is offset from BASE position, not object origin + + self.rotationSpeed = util.toRadians(config.getParameter("rotationSpeed")) + self.offAngle = util.toRadians(config.getParameter("offAngle", -30)) + + -- Targeting + self.targetQueryRange = config.getParameter("targetQueryRange") + self.targetMinRange = config.getParameter("targetMinRange") + self.targetMaxRange = config.getParameter("targetMaxRange") + self.targetAngleRange = util.toRadians(config.getParameter("targetAngleRange")) + + -- Energy + storage.energy = storage.energy or 0 + self.regenBlockTimer = 0 + self.energyRegen = config.getParameter("energyRegen") + self.maxEnergy = config.getParameter("maxEnergy") + self.energyRegenBlock = config.getParameter("energyRegenBlock") + + self.energyBarOffset = config.getParameter("energyBarOffset") + self.verticalScaling = config.getParameter("verticalScaling") + animator.translateTransformationGroup("energy", self.energyBarOffset) + + -- Initialize turret + object.setInteractive(false) + + self.state = FSM:new() + self.state:set(offState) +end + +function update(dt) + self.state:update(dt) + + world.debugPoint(firePosition(), "green") + + if storage.energy == 0 then + self.blockEnergyUsage = true + elseif storage.energy == self.maxEnergy then + self.blockEnergyUsage = false + end + + if self.regenBlockTimer > 0 then + self.regenBlockTimer = math.max(0, self.regenBlockTimer - script.updateDt()) + else + storage.energy = math.min(self.maxEnergy, storage.energy + self.energyRegen * script.updateDt()) + end + + local ratio = storage.energy / self.maxEnergy + local animationState = "full" + + if ratio <= 0.75 then animationState = "high" end + if ratio <= 0.5 then animationState = "medium" end + if ratio <= 0.25 then animationState = "low" end + if ratio <= 0 then animationState = "none" end + + local scale = self.verticalScaling and {1, ratio * 11} or {ratio * 11, 1} + + animator.resetTransformationGroup("energy") + animator.scaleTransformationGroup("energy", scale) + animator.translateTransformationGroup("energy", self.energyBarOffset) + + animator.setAnimationState("energy", animationState) +end + +---------------------------------------------------------------------------------------------------------- +-- States + +function offState() + animator.setAnimationState("attack", "dead") + animator.playSound("powerDown") + object.setAllOutputNodes(false) + + while true do + animator.rotateGroup("gun", self.offAngle) + + if active() then break end + coroutine.yield() + end + + animator.playSound("powerUp") + + self.state:set(scanState) +end + +function scanState() + animator.setAnimationState("attack", "idle") + util.wait(0.5) + animator.playSound("scan") + object.setAllOutputNodes(false) + + local timer = 0 + + local scanInterval = config.getParameter("scanInterval") + local scanAngle = util.toRadians(config.getParameter("scanAngle")) + + local scan = coroutine.wrap(function() + while true do + local target = findTarget() + if target then return self.state:set(fireState, target) end + util.wait(1.0) + end + end) + + while true do + timer = timer + script.updateDt() / scanInterval + if timer > 1 then timer = 0 end + animator.rotateGroup("gun", scanAngle * math.sin(timer * math.pi*2)) + + scan() + + if not active() then break end + coroutine.yield() + end + + self.state:set(offState) +end + +function fireState(targetId) + animator.setAnimationState("attack", "attack") + animator.playSound("foundTarget") + object.setAllOutputNodes(true) + + local maxFireAngle = util.toRadians(config.getParameter("maxFireAngle")) + local fire = coroutine.wrap(autoFire) + + while true do + if not active() then return self.state:set(offState) end + + if not world.entityExists(targetId) then break end + + local targetPosition = world.entityPosition(targetId) + local toTarget = world.distance(targetPosition, self.basePosition) + local targetDistance = world.magnitude(toTarget) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + + if targetDistance > self.targetMaxRange or targetDistance < self.targetMinRange or world.lineTileCollision(self.basePosition, targetPosition) then break end + if math.abs(targetAngle) > self.targetAngleRange then break end + + animator.rotateGroup("gun", targetAngle) + + local rotation = animator.currentRotationAngle("gun") + if math.abs(util.angleDiff(targetAngle, rotation)) < maxFireAngle then + fire() + end + coroutine.yield() + end + + util.wait(1.0) + + self.state:set(scanState) +end + +---------------------------------------------------------------------------------------------------------- +-- Helping functions, not states + +function consumeEnergy(amount) + if storage.energy <= 0 or self.blockEnergyUsage then return false end + storage.energy = storage.energy - amount + self.regenBlockTimer = self.energyRegenBlock + return true +end + +function active() + if object.isInputNodeConnected(0) then + return object.getInputNodeLevel(0) + end + + storage.active = storage.active ~= nil and storage.active or true + return storage.active +end + +function firePosition() + local animationPosition = vec2.div(config.getParameter("animationPosition"), 8) + local fireOffset = vec2.add(animationPosition, animator.partPoint("gun", "projectileSource")) + return vec2.add(object.position(), fireOffset) +end + +-- Coroutine +function autoFire() + local level = math.max(1.0, world.threatLevel()) + local power = 0.5 * math.max(1.0, world.threatLevel()) + local fireTime = 0.01 + local projectileParameters = { power = power } + local energyUsage = 2 + + while true do + while not consumeEnergy(energyUsage) do coroutine.yield() end + + local rotation = animator.currentRotationAngle("gun") + local aimVector = {object.direction() * math.cos(rotation), math.sin(rotation)} + world.spawnProjectile("flamethrower", firePosition(), entity.id(), aimVector, false, projectileParameters) + animator.playSound("fire") + util.wait(fireTime) + end +end + +-- Coroutine +function findTarget() + local nearEntities = world.entityQuery(self.basePosition, self.targetQueryRange, { includedTypes = { "monster", "npc", "player" } }) + return util.find(nearEntities, function(entityId) + local targetPosition = world.entityPosition(entityId) + if not entity.isValidTarget(entityId) or world.lineTileCollision(self.basePosition, targetPosition) then return false end + + local toTarget = world.distance(targetPosition, self.basePosition) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + return world.magnitude(toTarget) > self.targetMinRange and math.abs(targetAngle) < self.targetAngleRange + end) +end diff --git a/objects/wired/fu_turrets/fu_flameturret/turretgun.frames b/objects/wired/fu_turrets/fu_flameturret/turretgun.frames new file mode 100644 index 00000000000..67860cfddf9 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/turretgun.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 11], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/turretgun.png b/objects/wired/fu_turrets/fu_flameturret/turretgun.png new file mode 100644 index 00000000000..a2e66c14d3b Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/turretgun.png differ diff --git a/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.frames b/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.frames new file mode 100644 index 00000000000..67860cfddf9 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 11], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.png b/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.png new file mode 100644 index 00000000000..c0808918a63 Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/turretgunfullbright.png differ diff --git a/objects/wired/fu_turrets/fu_flameturret/turretstand.frames b/objects/wired/fu_turrets/fu_flameturret/turretstand.frames new file mode 100644 index 00000000000..9f280f58294 --- /dev/null +++ b/objects/wired/fu_turrets/fu_flameturret/turretstand.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 32], + "dimensions" : [3, 1], + + "names" : [ + [ "bottom", "left", "top"] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_flameturret/turretstand.png b/objects/wired/fu_turrets/fu_flameturret/turretstand.png new file mode 100644 index 00000000000..8fa0428ad12 Binary files /dev/null and b/objects/wired/fu_turrets/fu_flameturret/turretstand.png differ diff --git a/objects/wired/fu_turrets/fu_gaussturret/fu_gaussturret.object b/objects/wired/fu_turrets/fu_gaussturret/fu_gaussturret.object index 3a60bf5c483..c6a57dbc26b 100644 --- a/objects/wired/fu_turrets/fu_gaussturret/fu_gaussturret.object +++ b/objects/wired/fu_turrets/fu_gaussturret/fu_gaussturret.object @@ -10,7 +10,7 @@ "uiConfig" : "/interface/turret/standingturret.config", "frameCooldown" : 5, "autoCloseCooldown" : 3600, - + "health" : 5, "description" : "Turret for your protection! Can only be placed once.", "shortdescription" : "Gauss Turret", "subtitle" : "Automatic Base Defense", @@ -18,7 +18,7 @@ "category" : "wire", "breakDropOptions" : [ - [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ] ] + [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ], [ "fugaussrifle", 1, { } ] ] ], "apexDescription" : "Always watchful.", diff --git a/objects/wired/fu_turrets/fu_laserturret/emptyenergy.frames b/objects/wired/fu_turrets/fu_laserturret/emptyenergy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/emptyenergy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/emptyenergy.png b/objects/wired/fu_turrets/fu_laserturret/emptyenergy.png new file mode 100644 index 00000000000..bf2a5ba3113 Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/emptyenergy.png differ diff --git a/objects/wired/fu_turrets/fu_laserturret/energy.frames b/objects/wired/fu_turrets/fu_laserturret/energy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/energy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/energy.png b/objects/wired/fu_turrets/fu_laserturret/energy.png new file mode 100644 index 00000000000..2448d72a966 Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/energy.png differ diff --git a/objects/wired/fu_turrets/fu_laserturret/fu_laserturret.object b/objects/wired/fu_turrets/fu_laserturret/fu_laserturret.object new file mode 100644 index 00000000000..85414e21e76 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/fu_laserturret.object @@ -0,0 +1,196 @@ +{ + "objectName" : "fu_laserturret", + "colonyTags" : ["wired","combat"], + "printable" : false, + "rarity" : "Common", + "objectType" : "container", + "price" : 150, + + "slotCount" : 0, + "uiConfig" : "/interface/turret/standingturret.config", + "frameCooldown" : 5, + "autoCloseCooldown" : 3600, + "health" : 5, + "description" : "Turret for your protection! Can only be placed once.", + "shortdescription" : "Laser Turret", + "subtitle" : "Automatic Base Defense", + "race" : "generic", + "category" : "wire", + + "breakDropOptions" : [ + [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ], [ "lasrifle", 1, { } ] ] + ], + + "apexDescription" : "Always watchful.", + "avianDescription" : "The monsters don't stand a chance, unless they can fly.", + "floranDescription" : "Effective againssst living thingsss.", + "glitchDescription" : "Cautious. Must not disrupt my brother in his vigilance.", + "humanDescription" : "An automated security system.", + "hylotlDescription" : "Looks like a turret of some sort.", + "novakidDescription" : "Looks like this thing sure could do some damage!", + + "inventoryIcon" : "icon.png", + "orientations" : [ + { + "dualImage" : "turretstand.png:bottom", + + "imagePosition" : [-16, 0], + "imageLayers" : [ { "image" : "turretstand.png:bottom" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, 0], [-1, 0], [0, 0], [1, 0], + [-2, 1], [-1, 1], [0, 1], [1, 1] + ], + "anchors" : [ "bottom" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:bottom", + "energy" : "energy.png" + }, + "animationPosition" : [0, 16], + "animationCenterLine" : 0, + + "baseOffset" : [0, 2], + "energyBarOffset" : [0.375, -1.5], + "verticalScaling" : false + }, + { + "dualImage" : "turretstand.png:top", + + "imagePosition" : [-16, -24], + "imageLayers" : [ { "image" : "turretstand.png:top" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, -1], [-1, -1], [0, -1], [1, -1], + [-2, 0], [-1, 0], [0, 0], [1, 0] + ], + "anchors" : [ "top" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:top", + "energy" : "energy.png" + }, + "animationPosition" : [0, -8], + "animationCenterLine" : 0, + + "baseOffset" : [0, -1], + "energyBarOffset" : [0.375, 1.375], + "verticalScaling" : false + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [0, 0], + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [0, 0], [1, 0], + [0, 1], [1, 1], + [0, 2], [1, 2] + ], + "anchors" : [ "left" ], + "direction" : "right", + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [16, 16], + + "baseOffset" : [2, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [-24, 0], + "spaces" : [ + [-1, 0], [0, 0], + [-1, 1], [0, 1], + [-1, 2], [0, 2] + ], + "anchors" : [ "right" ], + "direction" : "left", + + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "flipImages" : true, + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [-10, 16], + + "baseOffset" : [-1, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + } + ], + + "objectWidth" : 4, + + "animation" : "standingturret.animation", + + "scripts" : [ + "/objects/wired/fu_turrets/fu_laserturret/standingturret.lua", + "/scripts/npcToyObject.lua", + "/scripts/stateMachine.lua", + "/scripts/util.lua", + "/scripts/vec2.lua" + ], + + "damageTeam" : { + "type" : "assistant" + }, + + "scriptDelta" : 5, + + "outputNodes" : [ [0, 0] ], + "inputNodes" : [ [0, 1] ], + + "rotationSpeed" : 25, + + "tipOffset" : [2.5, 0], + "offAngle" : -30, + + "scanInterval" : 6, + "scanAngle" : 30, + "scanRange" : 35, + + "targetQueryRange" : 35, + "targetMinRange" : 2.5, + "targetMaxRange" : 50, + "targetAngleRange" : 75, + + "maxFireAngle" : 5, + + "energyUsage" : 4, + "power" : 1, + "fireTime" : 0.1, + + "maxEnergy" : 100, + "energyRegen" : 25, + "energyRegenBlock" : 1.0, + + "npcToy" : { + "influence" : [ + "turret" + ], + "defaultReactions" : { + "turret" : [ + [1.0, "pressbutton"], + [1.0, "oh"], + [1.0, "laugh"] + ] + }, + "preciseStandPositionLeft" : [-1.0, 0.0], + "preciseStandPositionRight" : [1.0, 0.0], + "maxNpcs" : 1 + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/icon.png b/objects/wired/fu_turrets/fu_laserturret/icon.png new file mode 100644 index 00000000000..b7e30de972a Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/icon.png differ diff --git a/objects/wired/fu_turrets/fu_laserturret/standingturret.animation b/objects/wired/fu_turrets/fu_laserturret/standingturret.animation new file mode 100644 index 00000000000..4915b8c62cb --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/standingturret.animation @@ -0,0 +1,166 @@ +{ + "animatedParts" : { + "stateTypes" : { + "attack" : { + "priority" : 0, + "default" : "dead", + + "states" : { + "idle" : { + "frames" : 1 + }, + "dead" : { + "frames" : 1 + }, + "attack" : { + "frames" : 4, + "cycle" : 0.25, + "mode" : "loop" + } + } + }, + "energy" : { + "default" : "full", + + "states" : { + "full" : { "frames" : 1 }, + "high" : { "frames" : 1 }, + "medium" : { "frames" : 1 }, + "low" : { "frames" : 1 }, + "none" : { "frames" : 1 } + } + }, + "opened" : { + "default" : "true", + + "states" : { + "true" : { "frames" : 1 }, + "false" : { "frames" : 1 } + } + } + }, + + "parts" : { + "gun" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [3.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "fullbright" : true, + "zLevel" : 3 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "gunfullbright" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [3.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "zLevel" : 4 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "stand" : { + "properties" : { + "offset" : [0, 0], + "image" : "", + "centered" : true, + "zLevel" : 1 + } + }, + + "energy" : { + "properties" : { + "centered" : false, + "zLevel" : 2, + "transformationGroups" : [ "energy" ] + }, + + "partStates" : { + "energy" : { + "full" : { + "properties" : { "image" : ":full" } + }, + "high" : { + "properties" : { "image" : ":high" } + }, + "medium" : { + "properties" : { "image" : ":medium" } + }, + "low" : { + "properties" : { "image" : ":low" } + }, + "none" : { + "properties" : { "image" : ":none" } + } + } + } + } + } + }, + + "rotationGroups" : { + "gun" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + }, + "gunfullbright" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + } + }, + + "transformationGroups" : { + "energy" : { "interpolated" : false } + }, + + "sounds" : { + "powerUp" : ["/sfx/tech/mech_jump3.ogg"], + "powerDown" : ["/sfx/tech/mech_powerdown2.ogg"], + "foundTarget" : ["/sfx/interface/nav_computer_on.ogg"], + "scan" : ["/sfx/interface/scan.ogg"], + "fire" : [ "/sfx/weapons/energypew.ogg" ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/standingturret.lua b/objects/wired/fu_turrets/fu_laserturret/standingturret.lua new file mode 100644 index 00000000000..909082cc32f --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/standingturret.lua @@ -0,0 +1,214 @@ +require "/scripts/util.lua" +require "/scripts/interp.lua" + +function init() + -- Positions and angles + self.baseOffset = config.getParameter("baseOffset") + self.basePosition = vec2.add(object.position(), self.baseOffset) + self.tipOffset = config.getParameter("tipOffset") --This is offset from BASE position, not object origin + + self.rotationSpeed = util.toRadians(config.getParameter("rotationSpeed")) + self.offAngle = util.toRadians(config.getParameter("offAngle", -30)) + + -- Targeting + self.targetQueryRange = config.getParameter("targetQueryRange") + self.targetMinRange = config.getParameter("targetMinRange") + self.targetMaxRange = config.getParameter("targetMaxRange") + self.targetAngleRange = util.toRadians(config.getParameter("targetAngleRange")) + + -- Energy + storage.energy = storage.energy or 0 + self.regenBlockTimer = 0 + self.energyRegen = config.getParameter("energyRegen") + self.maxEnergy = config.getParameter("maxEnergy") + self.energyRegenBlock = config.getParameter("energyRegenBlock") + + self.energyBarOffset = config.getParameter("energyBarOffset") + self.verticalScaling = config.getParameter("verticalScaling") + animator.translateTransformationGroup("energy", self.energyBarOffset) + + -- Initialize turret + object.setInteractive(false) + + self.state = FSM:new() + self.state:set(offState) +end + +function update(dt) + self.state:update(dt) + + world.debugPoint(firePosition(), "green") + + if storage.energy == 0 then + self.blockEnergyUsage = true + elseif storage.energy == self.maxEnergy then + self.blockEnergyUsage = false + end + + if self.regenBlockTimer > 0 then + self.regenBlockTimer = math.max(0, self.regenBlockTimer - script.updateDt()) + else + storage.energy = math.min(self.maxEnergy, storage.energy + self.energyRegen * script.updateDt()) + end + + local ratio = storage.energy / self.maxEnergy + local animationState = "full" + + if ratio <= 0.75 then animationState = "high" end + if ratio <= 0.5 then animationState = "medium" end + if ratio <= 0.25 then animationState = "low" end + if ratio <= 0 then animationState = "none" end + + local scale = self.verticalScaling and {1, ratio * 11} or {ratio * 11, 1} + + animator.resetTransformationGroup("energy") + animator.scaleTransformationGroup("energy", scale) + animator.translateTransformationGroup("energy", self.energyBarOffset) + + animator.setAnimationState("energy", animationState) +end + +---------------------------------------------------------------------------------------------------------- +-- States + +function offState() + animator.setAnimationState("attack", "dead") + animator.playSound("powerDown") + object.setAllOutputNodes(false) + + while true do + animator.rotateGroup("gun", self.offAngle) + + if active() then break end + coroutine.yield() + end + + animator.playSound("powerUp") + + self.state:set(scanState) +end + +function scanState() + animator.setAnimationState("attack", "idle") + util.wait(0.5) + animator.playSound("scan") + object.setAllOutputNodes(false) + + local timer = 0 + + local scanInterval = config.getParameter("scanInterval") + local scanAngle = util.toRadians(config.getParameter("scanAngle")) + + local scan = coroutine.wrap(function() + while true do + local target = findTarget() + if target then return self.state:set(fireState, target) end + util.wait(1.0) + end + end) + + while true do + timer = timer + script.updateDt() / scanInterval + if timer > 1 then timer = 0 end + animator.rotateGroup("gun", scanAngle * math.sin(timer * math.pi*2)) + + scan() + + if not active() then break end + coroutine.yield() + end + + self.state:set(offState) +end + +function fireState(targetId) + animator.setAnimationState("attack", "attack") + animator.playSound("foundTarget") + object.setAllOutputNodes(true) + + local maxFireAngle = util.toRadians(config.getParameter("maxFireAngle")) + local fire = coroutine.wrap(autoFire) + + while true do + if not active() then return self.state:set(offState) end + + if not world.entityExists(targetId) then break end + + local targetPosition = world.entityPosition(targetId) + local toTarget = world.distance(targetPosition, self.basePosition) + local targetDistance = world.magnitude(toTarget) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + + if targetDistance > self.targetMaxRange or targetDistance < self.targetMinRange or world.lineTileCollision(self.basePosition, targetPosition) then break end + if math.abs(targetAngle) > self.targetAngleRange then break end + + animator.rotateGroup("gun", targetAngle) + + local rotation = animator.currentRotationAngle("gun") + if math.abs(util.angleDiff(targetAngle, rotation)) < maxFireAngle then + fire() + end + coroutine.yield() + end + + util.wait(1.0) + + self.state:set(scanState) +end + +---------------------------------------------------------------------------------------------------------- +-- Helping functions, not states + +function consumeEnergy(amount) + if storage.energy <= 0 or self.blockEnergyUsage then return false end + storage.energy = storage.energy - amount + self.regenBlockTimer = self.energyRegenBlock + return true +end + +function active() + if object.isInputNodeConnected(0) then + return object.getInputNodeLevel(0) + end + + storage.active = storage.active ~= nil and storage.active or true + return storage.active +end + +function firePosition() + local animationPosition = vec2.div(config.getParameter("animationPosition"), 8) + local fireOffset = vec2.add(animationPosition, animator.partPoint("gun", "projectileSource")) + return vec2.add(object.position(), fireOffset) +end + +-- Coroutine +function autoFire() + local level = math.max(1.0, world.threatLevel()) + local power = 2 * math.max(1.0, world.threatLevel()) + local fireTime = 0.1 + local projectileParameters = { power = power, speed = 72, knockback = 2, inaccuracy = 0.001 } + local energyUsage = 6 + + while true do + while not consumeEnergy(energyUsage) do coroutine.yield() end + + local rotation = animator.currentRotationAngle("gun") + local aimVector = {object.direction() * math.cos(rotation), math.sin(rotation)} + world.spawnProjectile("fulaserpistol", firePosition(), entity.id(), aimVector, false, projectileParameters) + animator.playSound("fire") + util.wait(fireTime) + end +end + +-- Coroutine +function findTarget() + local nearEntities = world.entityQuery(self.basePosition, self.targetQueryRange, { includedTypes = { "monster", "npc", "player" } }) + return util.find(nearEntities, function(entityId) + local targetPosition = world.entityPosition(entityId) + if not entity.isValidTarget(entityId) or world.lineTileCollision(self.basePosition, targetPosition) then return false end + + local toTarget = world.distance(targetPosition, self.basePosition) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + return world.magnitude(toTarget) > self.targetMinRange and math.abs(targetAngle) < self.targetAngleRange + end) +end diff --git a/objects/wired/fu_turrets/fu_laserturret/turretgun.frames b/objects/wired/fu_turrets/fu_laserturret/turretgun.frames new file mode 100644 index 00000000000..39afa4673a2 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/turretgun.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 8], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/turretgun.png b/objects/wired/fu_turrets/fu_laserturret/turretgun.png new file mode 100644 index 00000000000..1f73044c2e5 Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/turretgun.png differ diff --git a/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.frames b/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.frames new file mode 100644 index 00000000000..39afa4673a2 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 8], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.png b/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.png new file mode 100644 index 00000000000..7ed8ea77a06 Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/turretgunfullbright.png differ diff --git a/objects/wired/fu_turrets/fu_laserturret/turretstand.frames b/objects/wired/fu_turrets/fu_laserturret/turretstand.frames new file mode 100644 index 00000000000..9f280f58294 --- /dev/null +++ b/objects/wired/fu_turrets/fu_laserturret/turretstand.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 32], + "dimensions" : [3, 1], + + "names" : [ + [ "bottom", "left", "top"] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_laserturret/turretstand.png b/objects/wired/fu_turrets/fu_laserturret/turretstand.png new file mode 100644 index 00000000000..82b8bd5a23a Binary files /dev/null and b/objects/wired/fu_turrets/fu_laserturret/turretstand.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.frames b/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.png b/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.png new file mode 100644 index 00000000000..bf2a5ba3113 Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/emptyenergy.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/energy.frames b/objects/wired/fu_turrets/fu_rocketturret/energy.frames new file mode 100644 index 00000000000..f8b9f976c93 --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/energy.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [1, 1], + "dimensions" : [5, 1], + + "names" : [ + [ "full", "high", "medium", "low", "none" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/energy.png b/objects/wired/fu_turrets/fu_rocketturret/energy.png new file mode 100644 index 00000000000..2448d72a966 Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/energy.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/fu_rocketturret.object b/objects/wired/fu_turrets/fu_rocketturret/fu_rocketturret.object new file mode 100644 index 00000000000..f20bc75199b --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/fu_rocketturret.object @@ -0,0 +1,196 @@ +{ + "objectName" : "fu_rocketturret", + "colonyTags" : ["wired","combat"], + "printable" : false, + "rarity" : "Common", + "objectType" : "container", + "price" : 150, + + "slotCount" : 0, + "uiConfig" : "/interface/turret/standingturret.config", + "frameCooldown" : 5, + "autoCloseCooldown" : 3600, + "health" : 5, + "description" : "Turret for your protection! Can only be placed once.", + "shortdescription" : "Tesla Turret", + "subtitle" : "Automatic Base Defense", + "race" : "generic", + "category" : "wire", + + "breakDropOptions" : [ + [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ], [ "teslagun", 1, { } ] ] + ], + + "apexDescription" : "Always watchful.", + "avianDescription" : "The monsters don't stand a chance, unless they can fly.", + "floranDescription" : "Effective againssst living thingsss.", + "glitchDescription" : "Cautious. Must not disrupt my brother in his vigilance.", + "humanDescription" : "An automated security system.", + "hylotlDescription" : "Looks like a turret of some sort.", + "novakidDescription" : "Looks like this thing sure could do some damage!", + + "inventoryIcon" : "icon.png", + "orientations" : [ + { + "dualImage" : "turretstand.png:bottom", + + "imagePosition" : [-16, 0], + "imageLayers" : [ { "image" : "turretstand.png:bottom" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, 0], [-1, 0], [0, 0], [1, 0], + [-2, 1], [-1, 1], [0, 1], [1, 1] + ], + "anchors" : [ "bottom" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:bottom", + "energy" : "energy.png" + }, + "animationPosition" : [0, 16], + "animationCenterLine" : 0, + + "baseOffset" : [0, 2], + "energyBarOffset" : [0.375, -1.5], + "verticalScaling" : false + }, + { + "dualImage" : "turretstand.png:top", + + "imagePosition" : [-16, -24], + "imageLayers" : [ { "image" : "turretstand.png:top" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [-2, -1], [-1, -1], [0, -1], [1, -1], + [-2, 0], [-1, 0], [0, 0], [1, 0] + ], + "anchors" : [ "top" ], + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:top", + "energy" : "energy.png" + }, + "animationPosition" : [0, -8], + "animationCenterLine" : 0, + + "baseOffset" : [0, -1], + "energyBarOffset" : [0.375, 1.375], + "verticalScaling" : false + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [0, 0], + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "spaces" : [ + [0, 0], [1, 0], + [0, 1], [1, 1], + [0, 2], [1, 2] + ], + "anchors" : [ "left" ], + "direction" : "right", + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [16, 16], + + "baseOffset" : [2, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + }, + { + "image" : "turretstand.png:left", + + "imagePosition" : [-24, 0], + "spaces" : [ + [-1, 0], [0, 0], + [-1, 1], [0, 1], + [-1, 2], [0, 2] + ], + "anchors" : [ "right" ], + "direction" : "left", + + "imageLayers" : [ { "image" : "turretstand.png:left" }, { "image" : "turretgun.png:dead", "position": [4, 12] } ], + "flipImages" : true, + + "animationParts" : { + "gun" : "turretgun.png", + "gunfullbright" : "turretgunfullbright.png", + "stand" : "turretstand.png:left", + "energy" : "energy.png" + }, + "animationPosition" : [-10, 16], + + "baseOffset" : [-1, 2], + "energyBarOffset" : [-1.5, -0.625], + "verticalScaling" : true + } + ], + + "objectWidth" : 4, + + "animation" : "standingturret.animation", + + "scripts" : [ + "/objects/wired/fu_turrets/fu_rocketturret/standingturret.lua", + "/scripts/npcToyObject.lua", + "/scripts/stateMachine.lua", + "/scripts/util.lua", + "/scripts/vec2.lua" + ], + + "damageTeam" : { + "type" : "assistant" + }, + + "scriptDelta" : 5, + + "outputNodes" : [ [0, 0] ], + "inputNodes" : [ [0, 1] ], + + "rotationSpeed" : 25, + + "tipOffset" : [2.5, 0], + "offAngle" : -30, + + "scanInterval" : 6, + "scanAngle" : 30, + "scanRange" : 35, + + "targetQueryRange" : 35, + "targetMinRange" : 2.5, + "targetMaxRange" : 50, + "targetAngleRange" : 75, + + "maxFireAngle" : 5, + + "energyUsage" : 4, + "power" : 1, + "fireTime" : 0.1, + + "maxEnergy" : 100, + "energyRegen" : 25, + "energyRegenBlock" : 1.0, + + "npcToy" : { + "influence" : [ + "turret" + ], + "defaultReactions" : { + "turret" : [ + [1.0, "pressbutton"], + [1.0, "oh"], + [1.0, "laugh"] + ] + }, + "preciseStandPositionLeft" : [-1.0, 0.0], + "preciseStandPositionRight" : [1.0, 0.0], + "maxNpcs" : 1 + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/icon.png b/objects/wired/fu_turrets/fu_rocketturret/icon.png new file mode 100644 index 00000000000..b7e30de972a Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/icon.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/standingturret.animation b/objects/wired/fu_turrets/fu_rocketturret/standingturret.animation new file mode 100644 index 00000000000..b83d96dcf5f --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/standingturret.animation @@ -0,0 +1,166 @@ +{ + "animatedParts" : { + "stateTypes" : { + "attack" : { + "priority" : 0, + "default" : "dead", + + "states" : { + "idle" : { + "frames" : 1 + }, + "dead" : { + "frames" : 1 + }, + "attack" : { + "frames" : 4, + "cycle" : 0.25, + "mode" : "loop" + } + } + }, + "energy" : { + "default" : "full", + + "states" : { + "full" : { "frames" : 1 }, + "high" : { "frames" : 1 }, + "medium" : { "frames" : 1 }, + "low" : { "frames" : 1 }, + "none" : { "frames" : 1 } + } + }, + "opened" : { + "default" : "true", + + "states" : { + "true" : { "frames" : 1 }, + "false" : { "frames" : 1 } + } + } + }, + + "parts" : { + "gun" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [3.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "fullbright" : true, + "zLevel" : 3 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "gunfullbright" : { + "properties" : { + "offset" : [0.5, 0.0], + "projectileSource" : [3.0, 0.0], + "rotationGroup" : "gun", + "centered" : true, + "zLevel" : 4 + }, + + "partStates" : { + "attack" : { + "idle" : { + "properties" : { + "image" : ":idle" + } + }, + "dead" : { + "properties" : { + "image" : ":dead" + } + }, + "attack" : { + "properties" : { + "image" : ":attack." + } + } + } + } + }, + + "stand" : { + "properties" : { + "offset" : [0, 0], + "image" : "", + "centered" : true, + "zLevel" : 1 + } + }, + + "energy" : { + "properties" : { + "centered" : false, + "zLevel" : 2, + "transformationGroups" : [ "energy" ] + }, + + "partStates" : { + "energy" : { + "full" : { + "properties" : { "image" : ":full" } + }, + "high" : { + "properties" : { "image" : ":high" } + }, + "medium" : { + "properties" : { "image" : ":medium" } + }, + "low" : { + "properties" : { "image" : ":low" } + }, + "none" : { + "properties" : { "image" : ":none" } + } + } + } + } + } + }, + + "rotationGroups" : { + "gun" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + }, + "gunfullbright" : { + "rotationCenter" : [0, 0], + "angularVelocity" : 1 + } + }, + + "transformationGroups" : { + "energy" : { "interpolated" : false } + }, + + "sounds" : { + "powerUp" : ["/sfx/tech/mech_jump3.ogg"], + "powerDown" : ["/sfx/tech/mech_powerdown2.ogg"], + "foundTarget" : ["/sfx/interface/nav_computer_on.ogg"], + "scan" : ["/sfx/interface/scan.ogg"], + "fire" : [ "/sfx/gun/rocket_fire1.ogg" ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/standingturret.lua b/objects/wired/fu_turrets/fu_rocketturret/standingturret.lua new file mode 100644 index 00000000000..c3cc094b512 --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/standingturret.lua @@ -0,0 +1,214 @@ +require "/scripts/util.lua" +require "/scripts/interp.lua" + +function init() + -- Positions and angles + self.baseOffset = config.getParameter("baseOffset") + self.basePosition = vec2.add(object.position(), self.baseOffset) + self.tipOffset = config.getParameter("tipOffset") --This is offset from BASE position, not object origin + + self.rotationSpeed = util.toRadians(config.getParameter("rotationSpeed")) + self.offAngle = util.toRadians(config.getParameter("offAngle", -30)) + + -- Targeting + self.targetQueryRange = config.getParameter("targetQueryRange") + self.targetMinRange = config.getParameter("targetMinRange") + self.targetMaxRange = config.getParameter("targetMaxRange") + self.targetAngleRange = util.toRadians(config.getParameter("targetAngleRange")) + + -- Energy + storage.energy = storage.energy or 0 + self.regenBlockTimer = 0 + self.energyRegen = config.getParameter("energyRegen") + self.maxEnergy = config.getParameter("maxEnergy") + self.energyRegenBlock = config.getParameter("energyRegenBlock") + + self.energyBarOffset = config.getParameter("energyBarOffset") + self.verticalScaling = config.getParameter("verticalScaling") + animator.translateTransformationGroup("energy", self.energyBarOffset) + + -- Initialize turret + object.setInteractive(false) + + self.state = FSM:new() + self.state:set(offState) +end + +function update(dt) + self.state:update(dt) + + world.debugPoint(firePosition(), "green") + + if storage.energy == 0 then + self.blockEnergyUsage = true + elseif storage.energy == self.maxEnergy then + self.blockEnergyUsage = false + end + + if self.regenBlockTimer > 0 then + self.regenBlockTimer = math.max(0, self.regenBlockTimer - script.updateDt()) + else + storage.energy = math.min(self.maxEnergy, storage.energy + self.energyRegen * script.updateDt()) + end + + local ratio = storage.energy / self.maxEnergy + local animationState = "full" + + if ratio <= 0.75 then animationState = "high" end + if ratio <= 0.5 then animationState = "medium" end + if ratio <= 0.25 then animationState = "low" end + if ratio <= 0 then animationState = "none" end + + local scale = self.verticalScaling and {1, ratio * 11} or {ratio * 11, 1} + + animator.resetTransformationGroup("energy") + animator.scaleTransformationGroup("energy", scale) + animator.translateTransformationGroup("energy", self.energyBarOffset) + + animator.setAnimationState("energy", animationState) +end + +---------------------------------------------------------------------------------------------------------- +-- States + +function offState() + animator.setAnimationState("attack", "dead") + animator.playSound("powerDown") + object.setAllOutputNodes(false) + + while true do + animator.rotateGroup("gun", self.offAngle) + + if active() then break end + coroutine.yield() + end + + animator.playSound("powerUp") + + self.state:set(scanState) +end + +function scanState() + animator.setAnimationState("attack", "idle") + util.wait(0.5) + animator.playSound("scan") + object.setAllOutputNodes(false) + + local timer = 0 + + local scanInterval = config.getParameter("scanInterval") + local scanAngle = util.toRadians(config.getParameter("scanAngle")) + + local scan = coroutine.wrap(function() + while true do + local target = findTarget() + if target then return self.state:set(fireState, target) end + util.wait(1.0) + end + end) + + while true do + timer = timer + script.updateDt() / scanInterval + if timer > 1 then timer = 0 end + animator.rotateGroup("gun", scanAngle * math.sin(timer * math.pi*2)) + + scan() + + if not active() then break end + coroutine.yield() + end + + self.state:set(offState) +end + +function fireState(targetId) + animator.setAnimationState("attack", "attack") + animator.playSound("foundTarget") + object.setAllOutputNodes(true) + + local maxFireAngle = util.toRadians(config.getParameter("maxFireAngle")) + local fire = coroutine.wrap(autoFire) + + while true do + if not active() then return self.state:set(offState) end + + if not world.entityExists(targetId) then break end + + local targetPosition = world.entityPosition(targetId) + local toTarget = world.distance(targetPosition, self.basePosition) + local targetDistance = world.magnitude(toTarget) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + + if targetDistance > self.targetMaxRange or targetDistance < self.targetMinRange or world.lineTileCollision(self.basePosition, targetPosition) then break end + if math.abs(targetAngle) > self.targetAngleRange then break end + + animator.rotateGroup("gun", targetAngle) + + local rotation = animator.currentRotationAngle("gun") + if math.abs(util.angleDiff(targetAngle, rotation)) < maxFireAngle then + fire() + end + coroutine.yield() + end + + util.wait(1.0) + + self.state:set(scanState) +end + +---------------------------------------------------------------------------------------------------------- +-- Helping functions, not states + +function consumeEnergy(amount) + if storage.energy <= 0 or self.blockEnergyUsage then return false end + storage.energy = storage.energy - amount + self.regenBlockTimer = self.energyRegenBlock + return true +end + +function active() + if object.isInputNodeConnected(0) then + return object.getInputNodeLevel(0) + end + + storage.active = storage.active ~= nil and storage.active or true + return storage.active +end + +function firePosition() + local animationPosition = vec2.div(config.getParameter("animationPosition"), 8) + local fireOffset = vec2.add(animationPosition, animator.partPoint("gun", "projectileSource")) + return vec2.add(object.position(), fireOffset) +end + +-- Coroutine +function autoFire() + local level = math.max(1.0, world.threatLevel()) + local power = 7 * math.max(1.0, world.threatLevel()) + local fireTime = 1.35 + local projectileParameters = { power = power , speed=42, knockback=10 } + local energyUsage = 20 + + while true do + while not consumeEnergy(energyUsage) do coroutine.yield() end + + local rotation = animator.currentRotationAngle("gun") + local aimVector = {object.direction() * math.cos(rotation), math.sin(rotation)} + world.spawnProjectile("rocketswarmlite", firePosition(), entity.id(), aimVector, false, projectileParameters) + animator.playSound("fire") + util.wait(fireTime) + end +end + +-- Coroutine +function findTarget() + local nearEntities = world.entityQuery(self.basePosition, self.targetQueryRange, { includedTypes = { "monster", "npc", "player" } }) + return util.find(nearEntities, function(entityId) + local targetPosition = world.entityPosition(entityId) + if not entity.isValidTarget(entityId) or world.lineTileCollision(self.basePosition, targetPosition) then return false end + + local toTarget = world.distance(targetPosition, self.basePosition) + local targetAngle = math.atan(toTarget[2], object.direction() * toTarget[1]) + return world.magnitude(toTarget) > self.targetMinRange and math.abs(targetAngle) < self.targetAngleRange + end) +end diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretgun.frames b/objects/wired/fu_turrets/fu_rocketturret/turretgun.frames new file mode 100644 index 00000000000..2db7059546a --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/turretgun.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 10], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretgun.png b/objects/wired/fu_turrets/fu_rocketturret/turretgun.png new file mode 100644 index 00000000000..565e08b72e0 Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/turretgun.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.frames b/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.frames new file mode 100644 index 00000000000..2db7059546a --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 10], + "dimensions" : [6, 1], + + "names" : [ + [ "attack.1", "attack.2", "attack.3", "attack.4", "idle", "dead" ] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.png b/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.png new file mode 100644 index 00000000000..3af0e6d6f95 Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/turretgunfullbright.png differ diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretstand.frames b/objects/wired/fu_turrets/fu_rocketturret/turretstand.frames new file mode 100644 index 00000000000..9f280f58294 --- /dev/null +++ b/objects/wired/fu_turrets/fu_rocketturret/turretstand.frames @@ -0,0 +1,10 @@ +{ + "frameGrid" : { + "size" : [32, 32], + "dimensions" : [3, 1], + + "names" : [ + [ "bottom", "left", "top"] + ] + } +} diff --git a/objects/wired/fu_turrets/fu_rocketturret/turretstand.png b/objects/wired/fu_turrets/fu_rocketturret/turretstand.png new file mode 100644 index 00000000000..82b8bd5a23a Binary files /dev/null and b/objects/wired/fu_turrets/fu_rocketturret/turretstand.png differ diff --git a/objects/wired/fu_turrets/fu_teslaturret/fu_teslaturret.object b/objects/wired/fu_turrets/fu_teslaturret/fu_teslaturret.object index e110ef3c07a..316ce23fdc2 100644 --- a/objects/wired/fu_turrets/fu_teslaturret/fu_teslaturret.object +++ b/objects/wired/fu_turrets/fu_teslaturret/fu_teslaturret.object @@ -10,7 +10,7 @@ "uiConfig" : "/interface/turret/standingturret.config", "frameCooldown" : 5, "autoCloseCooldown" : 3600, - + "health" : 5, "description" : "Turret for your protection! Can only be placed once.", "shortdescription" : "Tesla Turret", "subtitle" : "Automatic Base Defense", @@ -18,7 +18,7 @@ "category" : "wire", "breakDropOptions" : [ - [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ] ] + [ [ "siliconboard", 1, { } ], [ "wire", 1, { } ], [ "teslagun", 1, { } ] ] ], "apexDescription" : "Always watchful.", diff --git a/projectiles/guns/gausscannonshot/gausscannonshot.projectile b/projectiles/guns/gausscannonshot/gausscannonshot.projectile index fecfeb05bc9..ae8a3c7d85c 100644 --- a/projectiles/guns/gausscannonshot/gausscannonshot.projectile +++ b/projectiles/guns/gausscannonshot/gausscannonshot.projectile @@ -18,7 +18,8 @@ "file" : "/projectiles/explosions/gaussrifleboom/gaussrifleboom.config" } ], - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "physics" : "bullet", "emitters" : [ "sparks" ] } diff --git a/projectiles/guns/gausspistolshot/gausspistolshot.projectile b/projectiles/guns/gausspistolshot/gausspistolshot.projectile index 0c6f623fcc4..944a4193c2f 100644 --- a/projectiles/guns/gausspistolshot/gausspistolshot.projectile +++ b/projectiles/guns/gausspistolshot/gausspistolshot.projectile @@ -18,7 +18,8 @@ "file" : "/projectiles/explosions/smallregularexplosion/smallregularexplosion.config" } ], - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "physics" : "bullet", "emitters" : [ "sparks" ] } diff --git a/projectiles/guns/gaussrifleshot/gaussrifleshot.projectile b/projectiles/guns/gaussrifleshot/gaussrifleshot.projectile index 4404bf3b2ce..a967e453111 100644 --- a/projectiles/guns/gaussrifleshot/gaussrifleshot.projectile +++ b/projectiles/guns/gaussrifleshot/gaussrifleshot.projectile @@ -18,7 +18,8 @@ "file" : "/projectiles/explosions/smallregularexplosion/smallregularexplosion.config" } ], - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "physics" : "bullet", "emitters" : [ "sparks" ] } diff --git a/projectiles/guns/rocketswarm/rocketswarm.projectile b/projectiles/guns/rocketswarm/rocketswarm.projectile index 616ef628ac9..abe0ddbe7a0 100644 --- a/projectiles/guns/rocketswarm/rocketswarm.projectile +++ b/projectiles/guns/rocketswarm/rocketswarm.projectile @@ -37,6 +37,7 @@ } ], "power" : 5.0, - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "emitters" : [ "smoke", "fireplasma" ] } diff --git a/projectiles/guns/rocketswarm/rocketswarmlite.projectile b/projectiles/guns/rocketswarm/rocketswarmlite.projectile index 88fd3969bf6..7cb8557f151 100644 --- a/projectiles/guns/rocketswarm/rocketswarmlite.projectile +++ b/projectiles/guns/rocketswarm/rocketswarmlite.projectile @@ -20,6 +20,7 @@ } ], "power" : 5.0, - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "emitters" : [ "smoke", "fireplasma" ] } diff --git a/projectiles/guns/rocketswarm/swarmmissile.projectile b/projectiles/guns/rocketswarm/swarmmissile.projectile index 5e0b91a0b00..058fed6308b 100644 --- a/projectiles/guns/rocketswarm/swarmmissile.projectile +++ b/projectiles/guns/rocketswarm/swarmmissile.projectile @@ -20,6 +20,7 @@ } ], "power" : 5.0, - "damageKind" : "fireplasma", + //"damageKind" : "fireplasma", + "damageKind" : "nodamage", "emitters" : [ "smoke", "fireplasma" ] } diff --git a/quests/stations/extractor/fuquest_water.questtemplate b/quests/stations/extractor/fuquest_water.questtemplate index 50267dbde78..d903cb3ee31 100644 --- a/quests/stations/extractor/fuquest_water.questtemplate +++ b/quests/stations/extractor/fuquest_water.questtemplate @@ -1,6 +1,6 @@ { "id" : "fuquest_water", - "title" : "^cyan;Hyrogen and Oxygen", + "title" : "^cyan;Hydrogen and Oxygen", "text" : "You've gathered ^orange;water^reset;! If you want to gather ^green;Hydrogen,Oxygen and Sodium^reset;, collect some ^orange;water^reset; and then use an ^orange;Extraction Lab^reset; to create ^green;5 hydrogen^reset;!", "completionText" : "You'll eventually find other sources for these useful materials, but for now this is the best method you have access to. Experimentation will get you far!", "rewards" : [