require("logic.basicAnimator") require("logic.basicState") require("logic.emptyBoxCollider") function getHeliFromBaseEntity(ent) for k,v in pairs(global.helis) do if v.baseEnt == ent then return v end end return nil end function findNearestAvailableHeli(pos, force, requestingPlayer) local nearestHeli = nil local nearestDist = nil if global.helis then for k, curHeli in pairs(global.helis) do if curHeli.baseEnt.valid and curHeli.baseEnt.force == force and (not curHeli.baseEnt.get_driver() or (curHeli.hasRemoteController and curHeli.remoteController.driverIsBot)) then if not requestingPlayer or (not curHeli.remoteController or curHeli.remoteController.owner == requestingPlayer) then local curDist = getDistance(pos, curHeli.baseEnt.position) if (not nearestDist) or (nearestDist and curDist < nearestDist) then nearestDist = curDist nearestHeli = curHeli end end end end end return nearestHeli, nearestDist end local frameFixes = { 0, --1 0.015625, --2 0.046875, --3 0.0625, --4 0.078125, --5 0.109375, --6 0.125, --7 0.140625, --8 0.15625, --9 0.171875, --10 0.1796875, --11 0.1875, --12 0.203125, --13 0.21875, --14 0.2265625, --15 0.234375, --16 0.25, --17 0.265625, --18 0.2734375, --19 0.28125, --20 0.296875, --21 0.3125, --22 0.3203125, --23 0.328125, --24 0.34375, --25 0.359375, --26 0.375, --27 0.390625, --28 0.40625, --29 0.4375, --30 0.453125, --31 0.46875, --32 0.5, --33 0.515625, --34 0.546875, --35 0.5625, --36 0.578125, --37 0.609375, --38 0.625, --39 0.640625, --40 0.65625, --41 0.671875, --42 0.6796875, --43 0.6875, --44 0.703125, --45 0.71875, --46 0.7265625, --47 0.734375, --48 0.75, --49 0.765625, --50 0.7734375, --51 0.78125, --52 0.796875, --53 0.8125, --54 0.8203125, --55 0.828125, --56 0.84375, --57 0.859375, --58 0.875, --59 0.890625, --60 0.90625, --61 0.9375, --62 0.953125, --63 0.984375, --64 } --local modVersion = versionStrToInt(game.active_mods.HelicopterRevival) maxCollisionHeight = 2 local maxBobbing = 0.05 local bobbingPeriod = 8*60 local colliderMaxHealth = 999999 local IsEntityBurnerOutOfFuel = function(ent) return ent.burner.remaining_burning_fuel <= 0 and ent.burner.inventory.is_empty() end local transferGridEquipment = function(srcEnt, destEnt) if srcEnt.grid and destEnt.grid then --assume they have the same size and destEnt.grid is empty. for i, equip in ipairs(srcEnt.grid.equipment) do local newEquip = destEnt.grid.put{name = equip.name, position = equip.position} if equip.type == "energy-shield-equipment" then newEquip.shield = equip.shield end newEquip.energy = equip.energy end srcEnt.grid.clear() end end heliEntityNames = "" heliBaseEntityNames = "" stateFuncs = { landed = { init = function(heli) heli.baseEnt.effectivity_modifier = 0 heli.baseEnt.friction_modifier = 50 heli.lockedBaseOrientation = heli.baseEnt.orientation heli.landedColliderCreationDelay = 2 heli:setFloodlightEntities(false) for k, curGG in pairs(heli.gaugeGuis) do curGG:setPointerNoise("gauge_fs", "speed", false) curGG:setPointerNoise("gauge_hr", "height", false) curGG:setPointerNoise("gauge_hr", "rpm", false) end heli:setFuelGaugeTarget(0, true) end, OnTick = function(heli) if heli.landedColliderCreationDelay > 0 then if heli.landedColliderCreationDelay == 1 then heli:setCollider("landed") heli:updateEntityRotations() end heli.landedColliderCreationDelay = heli.landedColliderCreationDelay - 1 end if heli.baseEnt.orientation ~= heli.lockedBaseOrientation then heli.baseEnt.orientation = heli.lockedBaseOrientation end local speed = heli.baseEnt.speed if speed > 0.25 then --54 km/h local players = getCarPlayers(heli.baseEnt) heli.baseEnt.damage(speed * 210, game.forces.neutral) if not heli.baseEnt.valid then --destroy event might already be executed heli:dealCrashDamage(players, speed) return false end end end, OnUp = function(heli) heli:changeState(heli.engineStarting) end, }, engineStarting = { init = function(heli) heli.lockedBaseOrientation = heli.baseEnt.orientation heli:setRotorTargetRPF(heli.rotorMaxRPF) if not (heli.burnerDriver and heli.burnerDriver.valid) then heli.burnerDriver = heli.surface.create_entity{name="character", force = game.forces.neutral, position = heli.baseEnt.position} heli.childs.burnerEnt.set_driver(heli.burnerDriver) end if heli.floodlightEnabled then heli:setFloodlightEntities(true) end for k, curGG in pairs(heli.gaugeGuis) do curGG:setPointerNoise("gauge_fs", "speed", true, 5) curGG:setPointerNoise("gauge_hr", "height", true, 0.5, 0.2, 12, 18) curGG:setPointerNoise("gauge_hr", "rpm", true, 50) end heli.lastFuelGaugeTargetVal = 1 end, OnTick = function(heli) if heli.baseEnt.orientation ~= heli.lockedBaseOrientation then heli.baseEnt.orientation = heli.lockedBaseOrientation end heli:handleFuelConsumption() heli:landIfEmpty() if heli.rotorRPF == heli.rotorMaxRPF then heli:changeState(heli.ascend) end end, }, ascend = { init = function(heli) heli.baseEnt.effectivity_modifier = 1 heli.baseEnt.friction_modifier = 1 local time = heli:setTargetHeight(heli.maxHeight) --heli.bobbingAnimator = basicAnimator.new(heli.curBobbing, 0, time*60, "linear") heli:setCollider("flying") end, OnTick = function(heli) heli:updateEntityRotations() heli:handleFuelConsumption() heli:landIfEmpty() heli:handleInserters() --if heli.bobbingAnimator and not heli.bobbingAnimator.isDone then -- heli.curBobbing = heli.bobbingAnimator:nextFrame() --end if heli.height > maxCollisionHeight then heli:setCollider("none") end if heli.height == heli.maxHeight then heli:changeState(heli.hovering) end end, OnMaxHeightChanged = function(heli) heli:setTargetHeight(heli.maxHeight) end, }, hovering = { init = function(heli) --heli.bobbingAnimator = basicAnimator.new(0, maxBobbing, bobbingPeriod, "cyclicSine") end, OnTick = function(heli) heli:updateEntityRotations() heli:handleFuelConsumption() heli:landIfEmpty() heli:handleInserters() --[[ local isDone heli.curBobbing, isDone = heli.bobbingAnimator:nextFrame() if isDone then heli.bobbingAnimator:reset() end ]] end, OnMaxHeightChanged = function(heli) heli:setTargetHeight(heli.maxHeight) end, }, descend = { init = function(heli) local time = heli:setTargetHeight(0) --heli.bobbingAnimator = basicAnimator.new(heli.curBobbing, 0, time*60, "linear") end, deinit = function(heli) heli:reactivateAllInserters() end, OnTick = function(heli) heli:updateEntityRotations() heli:handleFuelConsumption() heli:handleInserters() --if heli.bobbingAnimator and not heli.bobbingAnimator.isDone then -- heli.curBobbing = heli.bobbingAnimator:nextFrame() --end if heli.height <= maxCollisionHeight and not (heli.childs.collisionEnt and heli.childs.collisionEnt.valid) then heli:setCollider("flying") end if heli.height == 0 then heli:changeState(heli.engineStopping) end end, OnUp = function(heli) print("descend OnUp") heli:changeState(heli.ascend) end, }, engineStopping = { init = function(heli) heli.childs.burnerEnt.set_driver(nil) if heli.burnerDriver and heli.burnerDriver.valid then heli.burnerDriver.destroy() heli.burnerDriver = nil end heli:setRotorTargetRPF(0) heli:changeState(heli.landed) end, }, } heliBase = { ---------- default vals ----------- valid = true, goUp = false, startupProgress = 0, height = 0, targetHeight = 0, maxHeight = 5, curBobbing = 0, heightSpeed = 0, heightAcceleration = 0.001, maxHeightUperLimit = 20, maxHeightLowerLimit = 3, rotorOrient = 0, rotorRPF = 0, rotorTargetRPF = 0, fuelGaugeVal = 0, fuelGaugeTargetVal = 0, lastFuelGaugeTargetVal = 0, fuelGaugeSpeed = 0.005, gaugeGuis = {}, hasLandedCollider = false, landedColliderCreationDelay = 1, --frames. workaround for inserters trying to access collider inventory when created at the same time. floodlightEnabled = false, baseEngineConsumption = 20000, inserterScanRadius = 5, fullTankFlightTime = 15 * 60 * 60, --in ticks tankWarningRatio = 3 / 15, --3 min tankCriticalWarningRatio = 0.5 / 15, --30s ------------------------------------------------------------ new = function(placementEnt, baseEnt, childEnts, mt) transferGridEquipment(placementEnt, baseEnt) baseEnt.health = placementEnt.health local obj = { version = versionStrToInt(game.active_mods.HelicopterRevival), lockedBaseOrientation = baseEnt.orientation, baseEnt = baseEnt, childs = childEnts, surface = placementEnt.surface, deactivatedInserters = {}, } placementEnt.destroy() obj.baseEnt.effectivity_modifier = 0 for k,v in pairs(obj.childs) do if game.active_mods["Krastorio2"] then --Krastorio 2 workaround v.get_inventory(defines.inventory.fuel).insert({name = "fuel", count = 200}) elseif game.active_mods["SeaBlock"] then --SeaBlock workaround v.get_inventory(defines.inventory.fuel).insert({name = "cellulose-fiber", count = 200}) else v.get_inventory(defines.inventory.fuel).insert({name = "coal", count = 50}) end v.destructible = false end setmetatable(obj, mt) obj:changeState(obj.landed) return obj end, destroy = function(self) self.valid = false if self.baseEnt and self.baseEnt.valid then --self.baseEnt.destroy() end for k,v in pairs(self.childs) do if v and v.valid then v.destroy() end end if self.burnerDriver and self.burnerDriver.valid then self.burnerDriver.destroy() end if self.floodlightDriver and self.floodlightDriver.valid then self.floodlightDriver.destroy() end self:reactivateAllInserters() for k, curGG in pairs(self.gaugeGuis) do curGG:destroy() end end, ---------------- events ---------------- OnLoad = function(self) if self.curState then setmetatable(self.curState, basicState.mt) end if self.previousState then setmetatable(self.previousState, basicState.mt) end if self.hasLandedCollider and self.childs.collisionEnt then setmetatable(self.childs.collisionEnt, emptyBoxCollider.mt) end end, OnTick = function(self) if not self.baseEnt.valid or (self.childs.collisionEnt and not self.childs.collisionEnt.valid) then self:destroy() --Destroy child entities first if self.baseEnt and self.baseEnt.valid then self.baseEnt.destroy() --Re-check if the base entity is also valid, and destroy that as well end return end self:redirectPassengers() self:updateRotor() self:updateHeight() self:updateFuelGauge() self:updateEntityPositions() self.curState.OnTick(self) if self.valid then self:handleColliderDamage() end end, OnUp = function(self) self.curState.OnUp(self) end, OnDown = function(self) self:changeState(self.descend) end, OnIncreaseMaxHeight = function(self) self.maxHeight = math.min(self.maxHeightUperLimit, self.maxHeight + 1) self.curState.OnMaxHeightChanged(self) end, OnDecreaseMaxHeight = function(self) self.maxHeight = math.max(self.maxHeightLowerLimit, self.maxHeight - 1) self.curState.OnMaxHeightChanged(self) end, OnToggleFloodlight = function(self) self.floodlightEnabled = not self.floodlightEnabled if not self.floodlightEnabled then self:setFloodlightEntities(false) elseif self.height ~= 0 or self.rotorTargetRPF > 0 then self:setFloodlightEntities(true) end end, OnPlayerEjected = function(self) if self.childs.collisionEnt and self.hasLandedCollider then self.childs.collisionEnt.ejectPlayers() end end, ---------------- states ---------------- landed = basicState.new({ name = "landed", }), engineStarting = basicState.new({ name = "engineStarting", }), ascend = basicState.new({ name = "ascend", }), hovering = basicState.new({ name = "hovering", }), descend = basicState.new({ name = "descend", }), engineStopping = basicState.new({ name = "engineStopping", }), ---------------- utility --------------- reactivateAllInserters = function(self) for k, curInserter in pairs(self.deactivatedInserters) do if curInserter.valid then curInserter.active = true end end self.deactivatedInserters = {} end, reactivateSafeInserters = function(self) for i = #self.deactivatedInserters, 1, -1 do local curInserter = self.deactivatedInserters[i] if not curInserter.valid then table.remove(self.deactivatedInserters, i) elseif getDistance(curInserter.position, self.baseEnt.position) > self.inserterScanRadius then curInserter.active = true table.remove(self.deactivatedInserters, i) end end end, deactivateNearbyInserters = function(self) local p = self.baseEnt.position local area = {{p.x - self.inserterScanRadius, p.y - self.inserterScanRadius}, {p.x + self.inserterScanRadius, p.y + self.inserterScanRadius}} local inserters = self.surface.find_entities_filtered{ type = "inserter", area = area, } for k, curInserter in pairs(inserters) do if curInserter.active then curInserter.active = false table.insert(self.deactivatedInserters, curInserter) end end end, handleInserters = function(self) if settings.global["heli-deactivate-inserters"].value then self:deactivateNearbyInserters() self:reactivateSafeInserters() elseif #self.deactivatedInserters > 0 then self:reactivateAllInserters() end end, setTargetHeight = function(self, targetHeight) self.targetHeight = targetHeight return 60 end, setRotorTargetRPF = function(self, targetRPF) self.rotorTargetRPF = targetRPF end, setFuelGaugeTarget = function(self, targetFuel, suppressAlert) self.fuelGaugeTargetVal = targetFuel if not suppressAlert then local alert if self.fuelGaugeTargetVal <= self.tankCriticalWarningRatio and self.lastFuelGaugeTargetVal > self.tankCriticalWarningRatio then alert = {name = "signal-heli-fuel-warning-critical", str = {"heli-alert-fuel-warning-critical"}} elseif self.fuelGaugeTargetVal <= self.tankWarningRatio and self.lastFuelGaugeTargetVal > self.tankWarningRatio then alert = {name = "signal-heli-fuel-warning", str = {"heli-alert-fuel-warning"}} end if alert then local players = getCarPlayers(self.baseEnt) for k, curPlayer in pairs(players) do if curPlayer.mod_settings["heli-fuel-alert"].value then curPlayer.add_custom_alert(self.baseEnt, {type = "virtual", name = alert.name}, alert.str, false) end end end end self.lastFuelGaugeTargetVal = self.fuelGaugeTargetVal end, changeHeight = function(self, newHeight) local delta = newHeight - self.height local oldY = self.baseEnt.position.y self.baseEnt.teleport({x = self.baseEnt.position.x, y = self.baseEnt.position.y - delta}) if newHeight == self.targetHeight then self.height = self.targetHeight else --cant just apply the delta, the height would not reflect the sum of teleports, --causing the shadow to move down. --probably because of precision loss from lua->c++ / double->float self.height = self.height + oldY - self.baseEnt.position.y end end, landIfEmpty = function(self) local driver = self.baseEnt.get_driver() if not driver or not driver.valid or IsEntityBurnerOutOfFuel(self.baseEnt) then self:OnDown() end end, reassignCurState = function(self) if self.curState then local s = self[self.curState.name] if s and type(s) == "table" and s.name == self.curState.name then self.curState = s end end end, changeState = function(self, newState) --[[ for k,v in pairs(heli) do if v == newState then printA("change state: " .. k) break end end ]] self.previousState = self.curState if self.curState and self.curState.deinit then self.curState.deinit(self) end self.curState = newState if self.curState.init then self.curState.init(self) end end, insertIntoCar = function(self, car, player) if car and car.valid and player and player.valid then if not car.get_driver() then car.set_driver(player) return true end if not car.get_passenger() then car.set_passenger(player) return true end return false end end, redirectPassengers = function(self) for k, curChild in pairs(self.childs) do if curChild and curChild.valid then local curDriver = curChild.get_driver() local curPassenger = curChild.get_passenger() if curDriver and curDriver.valid then if k == "burnerEnt" and self.burnerDriver then if curDriver ~= self.burnerDriver then self:insertIntoCar(self.baseEnt, curDriver) curChild.set_driver(self.burnerDriver) end elseif k == "floodlightEnt" and self.floodlightDriver then if curDriver ~= self.floodlightDriver then self:insertIntoCar(self.baseEnt, curDriver) curChild.set_driver(self.floodlightDriver) end else if not self:insertIntoCar(self.baseEnt, curDriver) then curChild.set_driver(nil) end end end if curPassenger and curPassenger.valid then if not self:insertIntoCar(self.baseEnt, curPassenger) then curChild.set_passenger(nil) end end end end end, setCollider = function(self, name) if self.childs.collisionEnt and self.childs.collisionEnt.valid then self.childs.collisionEnt.destroy() self.childs.collisionEnt = nil self.hasLandedCollider = false end if name == "landed" then self.childs.collisionEnt = emptyBoxCollider.new({ surface = self.surface, position = self.baseEnt.position, orientation = self.baseEnt.orientation, force = game.forces.neutral, boxLengths = { ends = 3, sides = 4.8, }, nameEnds = "heli-landed-collision-end-entity-_-", nameSides = "heli-landed-collision-side-entity-_-", }) self.childs.collisionEnt.ejectPlayers() self.hasLandedCollider = true elseif name == "flying" then self.childs.collisionEnt = self.surface.create_entity{ name = "heli-flying-collision-entity-_-", force = game.forces.neutral, position = self.baseEnt.position, } end if self.childs.collisionEnt then if game.active_mods["Krastorio2"] then --Krastorio 2 workaround self.childs.collisionEnt.get_inventory(defines.inventory.fuel).insert({name = "fuel", count = 200}) elseif game.active_mods["SeaBlock"] then --SeaBlock workaround self.childs.collisionEnt.get_inventory(defines.inventory.fuel).insert({name = "cellulose-fiber", count = 200}) else self.childs.collisionEnt.get_inventory(defines.inventory.fuel).insert({name = "coal", count = 50}) end self.childs.collisionEnt.operable = false end end, setFloodlightEntities = function(self, enabled) if enabled then if not (self.childs.floodlightEnt and self.childs.floodlightEnt.valid) then self.childs.floodlightEnt = self.surface.create_entity{name = "heli-floodlight-entity-_-", force = game.forces.neutral, position = self.baseEnt.position} if game.active_mods["Krastorio2"] then --Krastorio 2 workaround self.childs.floodlightEnt.get_inventory(defines.inventory.fuel).insert({name = "fuel", count = 200}) elseif game.active_mods["SeaBlock"] then --SeaBlock workaround self.childs.collisionEnt.get_inventory(defines.inventory.fuel).insert({name = "cellulose-fiber", count = 200}) else self.childs.floodlightEnt.get_inventory(defines.inventory.fuel).insert({name = "coal", count = 50}) end end self.childs.floodlightEnt.orientation = self.baseEnt.orientation self.childs.floodlightEnt.operable = false if not (self.floodlightDriver and self.floodlightDriver.valid) then self.floodlightDriver = self.surface.create_entity{name="character", force = game.forces.neutral, position = self.baseEnt.position} end self.childs.floodlightEnt.set_driver(self.floodlightDriver) else if self.childs.floodlightEnt and self.childs.floodlightEnt.valid then self.childs.floodlightEnt.destroy() end self.childs.floodlightEnt = nil if self.floodlightDriver and self.floodlightDriver.valid then self.floodlightDriver.destroy() end self.floodlightDriver = nil end end, dealCrashDamage = function(self, players, speed) for k, curPlayer in pairs(players) do if curPlayer.character and curPlayer.character.valid then curPlayer.character.damage((150 + speed * 175) * settings.global["heli-crash-dmg-mult"].value, game.forces.neutral) end end end, handleColliderDamage = function(self) if self.childs.collisionEnt then if self.childs.collisionEnt.health ~= colliderMaxHealth then local players = getCarPlayers(self.baseEnt) local speed = self.childs.collisionEnt.speed self.baseEnt.speed = speed self.baseEnt.damage(colliderMaxHealth - self.childs.collisionEnt.health, game.forces.neutral) if not self.baseEnt.valid then --destroy event might already be executed self:dealCrashDamage(players, speed) return false end self.childs.collisionEnt.health = colliderMaxHealth end end return true end, getFuelFullness = function(self) local remainingFuel = self.baseEnt.burner.remaining_burning_fuel local bbInv = self.baseEnt.burner.inventory for i = 1, #bbInv do local curStack = bbInv[i] if curStack and curStack.valid_for_read then remainingFuel = remainingFuel + curStack.count * curStack.prototype.fuel_value end end if game.active_mods["Krastorio2"] then remainingFuel = remainingFuel * 16 elseif game.active_mods["SeaBlock"] then remainingFuel = remainingFuel * 9 end local burner = self.baseEnt.burner local full_value = 0 if burner.currently_burning then full_value = burner.currently_burning.fuel_value * burner.currently_burning.stack_size * #burner.inventory end if full_value > 0 then return remainingFuel / full_value else return 0 end end, handleFuelConsumption = function(self) self:consumeBaseFuel() self:setFuelGaugeTarget(self:getFuelFullness()) end, consumeBaseFuel = function(self) local baseBurner = self.baseEnt.burner baseBurner.remaining_burning_fuel = baseBurner.remaining_burning_fuel - self.baseEngineConsumption if baseBurner.remaining_burning_fuel <= 0 then if baseBurner.inventory.is_empty() then local mod = self.baseEnt.effectivity_modifier self.baseEnt.effectivity_modifier = 0 local driver = self.baseEnt.get_driver() if driver and driver.valid then driver.riding_state = {acceleration = defines.riding.acceleration.accelerating, direction = defines.riding.direction.straight} else driver = self.surface.create_entity{name = "character", force = self.baseEnt.force, position = self.baseEnt.position} self.baseEnt.set_driver(driver) driver.riding_state = {acceleration = defines.riding.acceleration.accelerating, direction = defines.riding.direction.straight} driver.destroy() self.baseEnt.set_driver(nil) end self.baseEnt.effectivity_modifier = mod else local fuelItemStack = nil for i = 1, #baseBurner.inventory do if baseBurner.inventory[i] and baseBurner.inventory[i].valid_for_read then fuelItemStack = baseBurner.inventory[i] break end end if fuelItemStack then baseBurner.currently_burning = fuelItemStack.name baseBurner.remaining_burning_fuel = fuelItemStack.prototype.fuel_value baseBurner.inventory.remove({name = fuelItemStack.name}) end end end if self.burnerDriver and self.burnerDriver.valid then self.burnerDriver.riding_state = {acceleration = defines.riding.acceleration.accelerating, direction = defines.riding.direction.straight} if self.childs.burnerEnt.burner.remaining_burning_fuel < 1000 then if game.active_mods["Krastorio2"] then --Krastorio 2 workaround self.childs.burnerEnt.get_inventory(defines.inventory.fuel).insert({name = "fuel", count = 1}) elseif game.active_mods["SeaBlock"] then --SeaBlock workaround self.childs.burnerEnt.get_inventory(defines.inventory.fuel).insert({name = "cellulose-fiber", count = 1}) else self.childs.burnerEnt.get_inventory(defines.inventory.fuel).insert({name = "coal", count = 1}) end end end end, updateRotor = function(self) if self.rotorRPF ~= self.rotorTargetRPF then if self.rotorRPF < self.rotorTargetRPF then self.rotorRPF = math.min(self.rotorRPF + self.rotorRPFacceleration, self.rotorTargetRPF) else self.rotorRPF = math.max(self.rotorRPF - self.rotorRPFacceleration, self.rotorTargetRPF) end end if self.rotorRPF > 0 then self.rotorOrient = self.rotorOrient + self.rotorRPF if self.rotorOrient > 1 then self.rotorOrient = self.rotorOrient - 1 end local frameFix = frameFixes[math.floor(self.rotorOrient * 64) + 1] self.childs.rotorEnt.orientation = frameFix self.childs.rotorEntShadow.orientation = frameFix end for k, curGG in pairs(self.gaugeGuis) do curGG:setGauge("gauge_hr", "rpm", self.rotorRPF * 3600 * self.engineReduction + math.abs(self.baseEnt.speed) * 100) end end, updateHeight = function(self) if self.height ~= self.targetHeight then local dir = 1 if self.targetHeight < self.height then dir = -1 end local desiredSpeed = (self.targetHeight - self.height) / 90 + dir * 0.005 if self.heightSpeed < desiredSpeed then self.heightSpeed = math.min(self.heightSpeed + self.heightAcceleration, desiredSpeed) else self.heightSpeed = math.max(self.heightSpeed - self.heightAcceleration, desiredSpeed) end local newHeight = self.height + self.heightSpeed if fEqual(newHeight, self.targetHeight, 0.01) then self:changeHeight(self.targetHeight) self.heightSpeed = 0 else self:changeHeight(newHeight) end end for k, curGG in pairs(self.gaugeGuis) do curGG:setGauge("gauge_hr", "height", self.height) end end, updateFuelGauge = function(self) if self.fuelGaugeVal ~= self.fuelGaugeTargetVal then if self.fuelGaugeVal < self.fuelGaugeTargetVal then self.fuelGaugeVal = math.min(self.fuelGaugeVal + self.fuelGaugeSpeed, self.fuelGaugeTargetVal) else self.fuelGaugeVal = math.max(self.fuelGaugeVal - self.fuelGaugeSpeed, self.fuelGaugeTargetVal) end end for k, curGG in pairs(self.gaugeGuis) do curGG:setGauge("gauge_fs", "fuel", self.fuelGaugeVal) if self.curState.name ~= "landed" then if self.fuelGaugeTargetVal <= self.tankCriticalWarningRatio then curGG:setLedBlinking("gauge_fs", "fuel", true, 20, "heli-fuel-warning") elseif self.fuelGaugeTargetVal <= self.tankWarningRatio then curGG:setLedBlinking("gauge_fs", "fuel", true, 60, "heli-fuel-warning") else curGG:setLedBlinking("gauge_fs", "fuel", false) end else curGG:setLedBlinking("gauge_fs", "fuel", false) end end end, updateEntityPositions = function(self) local baseVec = math3d.vector2.rotate({0,1}, math.pi * 2 * self.baseEnt.orientation) local vec = math3d.vector2.mul(baseVec, self.baseEnt.speed) local basePos = self.baseEnt.position self.childs.bodyEnt.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2] + self.bodyOffset - self.curBobbing}) self.childs.rotorEnt.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2] + self.rotorOffset - self.curBobbing}) self.childs.rotorEntShadow.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2] +self.height}) self.childs.bodyEntShadow.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2] + self.height}) if self.childs.floodlightEnt then local lightOffsetVec = math3d.vector2.mul(baseVec, self.height) self.childs.floodlightEnt.teleport({x = basePos.x - vec[1] - lightOffsetVec[1], y = basePos.y - vec[2] - lightOffsetVec[2] + self.height}) end if self.childs.collisionEnt then if not self.hasLandedCollider then local initVec = {0,1} local mul = 2 if self.baseEnt.speed < 0 then initVec = {0,-1} local x = self.baseEnt.orientation mul = math.abs(math.sin(math.pi*2*x))*1.2 + math.sin(math.pi*x) + 3 --dont ask end vec = math3d.vector2.mul(math3d.vector2.rotate(initVec, math.pi * 2 * self.baseEnt.orientation), mul) self.childs.collisionEnt.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2]}) self.childs.collisionEnt.speed = self.baseEnt.speed else self.childs.collisionEnt.teleport({x = basePos.x - vec[1], y = basePos.y - vec[2]}) self.childs.collisionEnt.speed = self.baseEnt.speed end end local off = (1 - math.sin(math.pi*self.baseEnt.orientation)) * 0.7 local center = {x = basePos.x, y = basePos.y - off} local radius = 2 snap = self.baseEnt.orientation snap = snap * (1 - math.sin(math.pi * snap)*0.05) snap = math.abs(snap * 64) / 64 local vec = math3d.vector2.mul(math3d.vector2.rotate({0,1}, math.pi * 2 * snap), radius) self.childs.burnerEnt.teleport({x = center.x + vec[1], y = center.y + vec[2] - self.curBobbing}) for k, curGG in pairs(self.gaugeGuis) do curGG:setGauge("gauge_fs", "speed", math.abs(self.baseEnt.speed) * 216) --speed * 60 = m/s, * 3.6 = km/h end end, updateEntityRotations = function(self) self.childs.bodyEnt.orientation = self.baseEnt.orientation self.childs.bodyEntShadow.orientation = self.baseEnt.orientation self.childs.burnerEnt.orientation = self.baseEnt.orientation if self.childs.collisionEnt then self.childs.collisionEnt.orientation = self.baseEnt.orientation end if self.childs.floodlightEnt then self.childs.floodlightEnt.orientation = self.baseEnt.orientation end end, isBaseOrChild = function(self, ent) if self.baseEnt == ent then return true end for k,v in pairs(self.childs) do if v == ent then return true end end if self.hasLandedCollider and self.childs.collisionEnt then return self.childs.collisionEnt.isChildEntity(ent) end return false end, addGaugeGui = function(self, gg) if #self.gaugeGuis == 0 then self.gaugeGuis = {} end table.insert(self.gaugeGuis, gg) end, removeGaugeGui = function(self, gg) for i, curGG in ipairs(self.gaugeGuis) do if curGG == gg then table.remove(self.gaugeGuis, i) return end end end, }