1117 lines
30 KiB
Lua
1117 lines
30 KiB
Lua
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,
|
|
}
|