New mission: Scoop

master
Armin Kretschmer 2019-12-23 11:12:38 +01:00 committed by Karl F
parent 62b8384558
commit 427b1c4a79
7 changed files with 1182 additions and 16 deletions

View File

@ -0,0 +1,198 @@
{
"ACCEPTED_ARMS_DEALER": {
"description": "",
"message": "Great! Thank you."
},
"ACCEPTED_ILLEGAL_GOODS": {
"description": "",
"message": "Thanks. You won't regret it!"
},
"ACCEPTED_LEGAL_GOODS": {
"description": "",
"message": "Thank you. You know a good deal when you see one!"
},
"ACCEPTED_RESCUE": {
"description": "",
"message": "Thank you very much. The crew will appreciate that."
},
"ADTEXT_ARMS_DEALER": {
"description": "",
"message": "PICK UP: Prompt recovery of drifting container required."
},
"ADTEXT_ILLEGAL_GOODS": {
"description": "",
"message": "EASY MONEY: Valuable information for sale."
},
"ADTEXT_LEGAL_GOODS": {
"description": "",
"message": "INFORMATION FOR SALE: Location of a debris field with valuable cargo."
},
"ADTEXT_RESCUE": {
"description": "",
"message": "HELP NEEDED: Urgent escape capsule rescue operation."
},
"CLIENT": {
"description": "",
"message": "Client:"
},
"DANGER": {
"description": "The risk level",
"message": "Danger:"
},
"DEADLINE": {
"description": "Must be delivered by",
"message": "Deadline:"
},
"DENY_1": {
"description": "",
"message": "Excuse me, sir? I don't think your current qualification is sufficient."
},
"DENY_2": {
"description": "",
"message": "Sorry, I think this task is beyond your ability as a pilot."
},
"DETONATORS": {
"description": "",
"message": "Detonators"
},
"DOCKING_INSTRUCTION": {
"description": "",
"message": "Nice to meet you. Please approach to within 100m to complete the cargo transfer!"
},
"FAILURE_MSG_ARMS_DEALER": {
"description": "",
"message": "You fool. The client's not gonna like this!"
},
"FAILURE_MSG_RESCUE": {
"description": "",
"message": "Your unreliability has caused great harm. Get out of my sight!"
},
"HOW_MUCH_TIME": {
"description": "",
"message": "How much time do I have?"
},
"HOW_MUCH_TIME_ARMS_DEALER": {
"description": "",
"message": "My business partner is in orbit around {star} to avoid detection. It would be great if you could reach him before {date}."
},
"HOW_MUCH_TIME_ILLEGAL_GOODS": {
"description": "",
"message": "Don't waste time. Only the early bird catches the worm!"
},
"HOW_MUCH_TIME_LEGAL_GOODS": {
"description": "",
"message": "I don't know. But if I were you, I would be quick!"
},
"HOW_MUCH_TIME_RESCUE": {
"description": "",
"message": "It would be good if you could reach the location before {date}."
},
"INTROTEXT_ARMS_DEALER": {
"description": "",
"message": "Hello Commander. My name is {client}. It would be great if you could help me out. An unreliable freighter captain dropped my cargo near {planet} and has disappeared since. Your task would be to pick up one container and deliver it to ship {shipid} in orbit around {star}. I would pay you {cash}."
},
"INTROTEXT_ILLEGAL_GOODS": {
"description": "",
"message": "Hey buddy, I know about a debris field with illegal goods. The police caught a smuggler close to {planet}. They haven't had time to clean up the area yet. If you are quick, you can make easy money! I can give you the coordinates. It only costs you {cash}. Don't let the chance slip by!"
},
"INTROTEXT_LEGAL_GOODS": {
"description": "",
"message": "Hello my friend. I'm {client}. I heard about a cargo hauler that got into trouble near {planet} because of bad ship maintenance. Amazing who gets to fly a spaceship these days, isn't it? Anyway, they had to dump a lot of the cargo. If you're quick, you can grab the cargo very easily! This info is as good as new! I'll sell you the coordinates for lousy {cash}. What do you say?"
},
"INTROTEXT_RESCUE": {
"description": "",
"message": "Hi, my name is {client}. The crew of a freighter had to abandon their ship after an incident. The ship owner is willing to pay {cash} for a discreet rescue mission. The accident site is near {planet}. Please pick up the crew and bring them to a station!"
},
"NUCLEAR_MISSILE": {
"description": "A cargo item",
"message": "Nuclear missile"
},
"OK_AGREED": {
"description": "",
"message": "OK, agreed."
},
"REPEAT_THE_REQUEST": {
"description": "",
"message": "Could you please repeat the request?"
},
"RESCUE_CAPSULE": {
"description": "",
"message": "Rescue capsule"
},
"ROCKET_LAUNCHERS": {
"description": "",
"message": "Rocket launchers"
},
"SCOOP": {
"description": "Name of mission type",
"message": "Scoop"
},
"SET_AS_TARGET": {
"description": "",
"message": "Set as navigation target"
},
"SHIP": {
"description": "",
"message": "Ship:"
},
"SHIP_DESTROYED": {
"description": "",
"message": "Oh no. {shipid} has been destroyed. Deliver the cargo to {station} now!"
},
"SPACEPORT": {
"description": "",
"message": "Spaceport:"
},
"SPOILED_FOOD": {
"description": "cargo item",
"message": "Spoiled food"
},
"SUCCESS_MSG_ARMS_DEALER": {
"description": "",
"message": "Hey Ace, nice flying! Money has been transferred."
},
"SUCCESS_MSG_RESCUE": {
"description": "",
"message": "Thank you very much! The crew would also like to express their thanks."
},
"TOXIC_WASTE": {
"description": "cargo item",
"message": "Toxic waste"
},
"UNKNOWN": {
"description": "",
"message": "Unknown"
},
"WARNING": {
"description": "",
"message": "Hey! What are you trying to do? Our contract is at risk! Please pick that up again!"
},
"WHY_NOT_YOURSELF": {
"description": "",
"message": "Why don't you do it yourself?"
},
"WHY_NOT_YOURSELF_ARMS_DEALER": {
"description": "",
"message": "The local authorities are watching me! I have to keep a low profile for a few weeks."
},
"WHY_NOT_YOURSELF_ILLEGAL_GOODS": {
"description": "",
"message": "I'm just an office clerk. I don't own a ship."
},
"WHY_NOT_YOURSELF_LEGAL_GOODS": {
"description": "",
"message": "I don't have a ship."
},
"WHY_NOT_YOURSELF_RESCUE": {
"description": "",
"message": "I have no ship available at this time."
},
"YOU_DO_NOT_HAVE_A_SCOOP": {
"description": "",
"message": "Sorry, you do not have a cargo scoop."
},
"YOU_DO_NOT_HAVE_ENOUGH_MONEY": {
"description": "",
"message": "You don't have enough money."
}
}

View File

@ -0,0 +1,857 @@
-- Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
local Game = require 'Game'
local Lang = require 'Lang'
local Ship = require 'Ship'
local Comms = require 'Comms'
local Event = require 'Event'
local Space = require 'Space'
local Timer = require 'Timer'
local Engine = require 'Engine'
local Format = require 'Format'
local Mission = require 'Mission'
local ShipDef = require 'ShipDef'
local Character = require 'Character'
local Equipment = require 'Equipment'
local Serializer = require 'Serializer'
local utils = require 'utils'
local l = Lang.GetResource("module-scoop")
local lc = Lang.GetResource("ui-core")
local AU = 149597870700.0
local LEGAL = 1
local ILLEGAL = 2
local mission_reputation = 1
local mission_time = 14*24*60*60
local max_dist = 20 * AU
local ads = {}
local missions = {}
local rescue_capsule = Equipment.EquipType.New({
name = "rescue_capsule",
l10n_key = "RESCUE_CAPSULE",
l10n_resource = "module-scoop",
slots = "cargo",
price = 500,
icon_name = "Default",
model_name = "escape_pod",
capabilities = { mass = 1, crew = 1 },
purchasable = false
})
local rocket_launchers = Equipment.EquipType.New({
name = "rocket_launchers",
l10n_key = "ROCKET_LAUNCHERS",
l10n_resource = "module-scoop",
slots = "cargo",
price = 500,
icon_name = "Default",
capabilities = { mass = 1 },
purchasable = false
})
local detonators = Equipment.EquipType.New({
name = "detonators",
l10n_key = "DETONATORS",
l10n_resource = "module-scoop",
slots = "cargo",
price = 250,
icon_name = "Default",
capabilities = { mass = 1 },
purchasable = false
})
local nuclear_missile = Equipment.EquipType.New({
name = "nuclear_missile",
l10n_key = "NUCLEAR_MISSILE",
l10n_resource = "module-scoop",
slots = "cargo",
price = 1250,
icon_name = "Default",
model_name = "missile",
capabilities = { mass = 1 },
purchasable = false
})
-- Useless waste that the player has to sort out
local toxic_waste = Equipment.EquipType.New({
name = "toxic_waste",
l10n_key = "TOXIC_WASTE",
l10n_resource = "module-scoop",
slots = "cargo",
price = -50,
icon_name = "Default",
capabilities = { mass = 1 },
purchasable = false
})
local spoiled_food = Equipment.EquipType.New({
name = "spoiled_food",
l10n_key = "SPOILED_FOOD",
l10n_resource = "module-scoop",
slots = "cargo",
price = -10,
icon_name = "Default",
capabilities = { mass = 1 },
purchasable = false
})
local unknown = Equipment.EquipType.New({
name = "unknown",
l10n_key = "UNKNOWN",
l10n_resource = "module-scoop",
slots = "cargo",
price = -5,
icon_name = "Default",
capabilities = { mass = 1 },
purchasable = false
})
local rescue_capsules = {
rescue_capsule
}
local weapons = {
rocket_launchers,
detonators,
nuclear_missile
}
local waste = {
toxic_waste,
spoiled_food,
unknown,
Equipment.cargo.radioactives,
Equipment.cargo.rubbish
}
local flavours = {
{
id = "LEGAL_GOODS",
cargo_type = nil,
reward = -500,
amount = 20,
},
{
id = "ILLEGAL_GOODS",
cargo_type = nil,
reward = -1000,
amount = 10,
},
{
id = "RESCUE",
cargo_type = rescue_capsules,
reward = 750,
amount = 4,
return_to_station = true,
},
{
id = "ARMS_DEALER",
cargo_type = weapons,
reward = 1000,
amount = 1,
deliver_to_ship = true,
},
}
-- Sort goods, legal and illegal
local sortGoods = function (goods)
local legal_goods = {}
local illegal_goods = {}
local system = Game.system
for _, e in pairs(goods) do
if e.purchasable and system:IsCommodityLegal(e.name) then
table.insert(legal_goods, e)
else
table.insert(illegal_goods, e)
end
end
return legal_goods, illegal_goods
end
-- Returns the number of flavours of the given string (assuming first flavour has suffix '_1').
local getNumberOfFlavours = function (str)
local num = 1
while l:get(str .. "_" .. num) do
num = num + 1
end
return num - 1
end
-- Create a debris field in a random distance to a system body
local spawnDebris = function (debris, amount, sbody, min, max, lifetime)
local list = {}
local cargo
local body = Space.GetBody(sbody:GetSystemBody().index)
for i = 1, Engine.rand:Integer(math.ceil(amount / 4), amount) do
cargo = debris[Engine.rand:Integer(1, #debris)]
body = Space.SpawnCargoNear(cargo, body, min, max, lifetime)
if i > 1 then body:SetVelocity(list[1].body:GetVelocity()) end
table.insert(list, { cargo = cargo, body = body })
min = 10
max = 1000
end
-- add some useless waste
for i = 1, Engine.rand:Integer(1, 9) do
cargo = waste[Engine.rand:Integer(1, #waste)]
body = Space.SpawnCargoNear(cargo, body, min, max, lifetime)
body:SetVelocity(list[1].body:GetVelocity())
end
return list
end
-- Create a couple of police ships
local spawnPolice = function (station)
local ship
local police = {}
local shipdef = ShipDef[Game.system.faction.policeShip]
for i = 1, 2 do
ship = Space.SpawnShipDocked(shipdef.id, station)
ship:SetLabel(lc.POLICE)
ship:AddEquip(Equipment.laser.pulsecannon_1mw)
table.insert(police, ship)
if station.type == "STARPORT_SURFACE" then
ship:AIEnterLowOrbit(Space.GetBody(station:GetSystemBody().parent.index))
end
end
Timer:CallAt(Game.time + 5, function ()
for _, s in pairs(police) do
s:AIKill(Game.player)
end
end)
return police
end
-- Returns a random system close to the players location
local nearbySystem = function ()
local dist = 5
local systems = {}
while #systems < 1 do
systems = Game.system:GetNearbySystems(dist)
dist = dist + 5
end
return systems[Engine.rand:Integer(1, #systems)].path
end
-- Create a ship in orbit
local spawnClientShip = function (star, ship_label)
local shipdefs = utils.build_array(utils.filter(
function (k, def)
return def.tag == "SHIP" and def.hyperdriveClass > 0 and def.equipSlotCapacity["scoop"] > 0
end,
pairs(ShipDef)))
local shipdef = shipdefs[Engine.rand:Integer(1, #shipdefs)]
local radius = star:GetSystemBody().radius
local min, max
if star:GetSystemBody().type == "WHITE_DWARF" then
min = radius * 30
max = radius * 40
else
min = radius * 3.5
max = radius * 4.5
end
local ship = Space.SpawnShipOrbit(shipdef.id, Space.GetBody(star:GetSystemBody().index), min, max)
ship:SetLabel(ship_label)
ship:AddEquip(Equipment.hyperspace["hyperdrive_" .. shipdef.hyperdriveClass])
ship:AddEquip(Equipment.laser.pulsecannon_2mw)
ship:AddEquip(Equipment.misc.shield_generator)
return ship
end
local removeMission = function (mission, ref)
local oldReputation = Character.persistent.player.reputation
local sender = mission.client_ship and mission.ship_label or mission.client.name
if mission.status == "COMPLETED" then
Character.persistent.player.reputation = oldReputation + mission_reputation
Game.player:AddMoney(mission.reward)
Comms.ImportantMessage(l["SUCCESS_MSG_" .. mission.id], sender)
elseif mission.status == "FAILED" then
Character.persistent.player.reputation = oldReputation - mission_reputation
Comms.ImportantMessage(l["FAILURE_MSG_" .. mission.id], sender)
end
Event.Queue("onReputationChanged", oldReputation, Character.persistent.player.killcount,
Character.persistent.player.reputation, Character.persistent.player.killcount)
if ref == nil then
for r, m in pairs(missions) do
if m == mission then ref = r break end
end
end
mission:Remove()
missions[ref] = nil
end
-- Cargo transfer to a ship
local transferCargo = function (mission, ref)
Timer:CallEvery(9, function ()
if not mission.client_ship then return true end
if not mission.docking_in_progress and Game.player:DistanceTo(mission.client_ship) <= 5000 then
mission.docking_in_progress = true
Comms.ImportantMessage(l.DOCKING_INSTRUCTION, mission.ship_label)
end
if Game.player:DistanceTo(mission.client_ship) <= 100 then
-- unload mission cargo
for i, e in pairs(mission.debris) do
if e.body == nil then
if Game.player:RemoveEquip(e.cargo, 1, "cargo") == 1 then
mission.client_ship:AddEquip(e.cargo, 1, "cargo")
mission.debris[i] = nil
mission.amount = mission.amount - 1
end
end
end
if mission.amount == 0 then
mission.status = "COMPLETED"
elseif mission.destination == nil then
mission.status = "FAILED"
end
end
if mission.status == "COMPLETED" or mission.status == "FAILED" then
local ship = mission.client_ship
mission.client_ship = nil
removeMission(mission, ref)
ship:HyperjumpTo(nearbySystem())
end
end)
end
local isQualifiedFor = function(reputation, ad)
return reputation > (ad.reward/100) or false
end
local onDelete = function (ref)
ads[ref] = nil
end
local isEnabled = function (ref)
return ads[ref] ~= nil and isQualifiedFor(Character.persistent.player.reputation, ads[ref])
end
local onChat = function (form, ref, option)
local ad = ads[ref]
local player = Game.player
local debris, ship, radius, mindist, maxdist
form:Clear()
if option == -1 then
form:Close()
return
end
local qualified = isQualifiedFor(Character.persistent.player.reputation, ad)
form:SetFace(ad.client)
if not qualified then
form:SetMessage(l["DENY_" .. Engine.rand:Integer(1, getNumberOfFlavours("DENY"))])
return
end
form:AddNavButton(ad.planet)
if option == 0 then
local introtext = string.interp(ad.introtext, {
client = ad.client.name,
shipid = ad.ship_label,
star = ad.star:GetSystemBody().name,
planet = ad.planet:GetSystemBody().name,
cash = Format.Money(math.abs(ad.reward), false),
})
form:SetMessage(introtext)
elseif option == 1 then
form:SetMessage(l["WHY_NOT_YOURSELF_" .. ad.id])
elseif option == 2 then
form:SetMessage(string.interp(l["HOW_MUCH_TIME_" .. ad.id], { star = ad.star:GetSystemBody().name, date = Format.Date(ad.due) }))
elseif option == 3 then
if ad.reward > 0 and player:CountEquip(Equipment.misc.cargo_scoop) == 0 and player:CountEquip(Equipment.misc.multi_scoop) == 0 then
form:SetMessage(l.YOU_DO_NOT_HAVE_A_SCOOP)
form:RemoveNavButton()
return
end
if ad.reward < 0 and player:GetMoney() < math.abs(ad.reward) then
form:SetMessage(l.YOU_DO_NOT_HAVE_ENOUGH_MONEY)
form:RemoveNavButton()
return
end
form:RemoveAdvertOnClose()
ads[ref] = nil
radius = ad.planet:GetSystemBody().radius
if ad.planet:GetSystemBody().superType == "ROCKY_PLANET" then
mindist = radius * 2.5
maxdist = radius * 3.5
else
mindist = radius * 25
maxdist = radius * 35
end
debris = spawnDebris(ad.debris_type, ad.amount, ad.planet, mindist, maxdist, ad.due - Game.time)
if ad.reward < 0 then player:AddMoney(ad.reward) end
if ad.deliver_to_ship then
ship = spawnClientShip(ad.star, ad.ship_label)
end
local mission = {
type = "Scoop",
location = ad.location,
introtext = ad.introtext,
client = ad.client,
station = ad.station.path,
star = ad.star,
planet = ad.planet,
id = ad.id,
debris = debris,
amount = #debris,
reward = ad.reward,
due = ad.due,
return_to_station = ad.return_to_station,
deliver_to_ship = ad.deliver_to_ship,
client_ship = ship,
ship_label = ad.ship_label,
destination = debris[1].body
}
table.insert(missions, Mission.New(mission))
form:SetMessage(l["ACCEPTED_" .. ad.id])
form:RemoveNavButton()
form:AddNavButton(debris[1].body)
return
end
form:AddOption(l.WHY_NOT_YOURSELF, 1)
form:AddOption(l.HOW_MUCH_TIME, 2)
form:AddOption(l.REPEAT_THE_REQUEST, 0)
form:AddOption(l.OK_AGREED, 3)
end
local getPlanets = function (system)
local planets = {}
for _, p in ipairs(system:GetBodyPaths()) do
if p:GetSystemBody().superType == "ROCKY_PLANET" or p:GetSystemBody().superType == "GAS_GIANT" then
table.insert(planets, p)
end
end
return planets
end
local planets = nil
local makeAdvert = function (station)
if planets == nil then planets = getPlanets(Game.system) end
if #planets == 0 then return end
if flavours[LEGAL].cargo_type == nil then
flavours[LEGAL].cargo_type, flavours[ILLEGAL].cargo_type = sortGoods(Equipment.cargo)
end
local stars = Game.system:GetStars()
local star = stars[Engine.rand:Integer(1, #stars)]
local planet = planets[Engine.rand:Integer(1, #planets)]
local dist = station:DistanceTo(Space.GetBody(planet:GetSystemBody().index))
local flavour = flavours[Engine.rand:Integer(1, #flavours)]
local due = Game.time + mission_time * (1 + dist / max_dist) * Engine.rand:Number(0.8, 1.2)
local reward
if flavour.reward < 0 then
reward = flavour.reward * (1.15 - dist / max_dist) * Engine.rand:Number(0.9, 1.1)
else
reward = flavour.reward * (1 + dist / max_dist) * Engine.rand:Number(0.75, 1.25)
end
reward = utils.round(reward, 50)
if #flavour.cargo_type > 0 and dist < max_dist and station:DistanceTo(Space.GetBody(star.index)) < max_dist then
local ad = {
station = station,
location = planet,
introtext = l["INTROTEXT_" .. flavour.id],
client = Character.New(),
star = star.path,
planet = planet,
id = flavour.id,
debris_type = flavour.cargo_type,
reward = math.ceil(reward),
amount = flavour.amount,
due = due,
return_to_station = flavour.return_to_station,
deliver_to_ship = flavour.deliver_to_ship,
ship_label = flavour.deliver_to_ship and Ship.MakeRandomLabel() or nil
}
ad.desc = string.interp(l["ADTEXT_" .. flavour.id], { cash = Format.Money(ad.reward, false) })
local ref = station:AddAdvert({
description = ad.desc,
icon = flavour.id == "RESCUE" and "searchrescue" or "haul",
onChat = onChat,
onDelete = onDelete,
isEnabled = isEnabled
})
ads[ref] = ad
end
end
local onCreateBB = function (station)
local num = Engine.rand:Integer(0, math.ceil(Game.system.population * Game.system.lawlessness))
for i = 1, num do
makeAdvert(station)
end
end
local onUpdateBB = function (station)
for ref, ad in pairs(ads) do
if ad.due < Game.time + 5*24*60*60 then -- five day timeout
ad.station:RemoveAdvert(ref)
end
end
if Engine.rand:Integer(4*24*60*60) < 60*60 then -- roughly once every four days
makeAdvert(station)
end
end
local onShipEquipmentChanged = function (ship, equipment)
if not ship:IsPlayer() or equipment == nil or equipment:GetDefaultSlot() ~= "cargo" then return end
for ref, mission in pairs(missions) do
if not mission.police and not Game.system:IsCommodityLegal(equipment.name) and not ship:IsDocked() and mission.location:IsSameSystem(Game.system.path) then
if (1 - Game.system.lawlessness) > Engine.rand:Number(4) then
local station = ship:FindNearestTo("SPACESTATION")
if station then mission.police = spawnPolice(station) end
end
end
end
end
-- The attacker could be a ship or the planet
-- If scooped or destroyed by self-destruction, attacker is nil
local onCargoDestroyed = function (body, attacker)
for ref, mission in pairs(missions) do
for i, e in pairs(mission.debris) do
if body == e.body then
e.body = nil
if body == mission.destination then
-- remove NavButton
mission.destination = nil
end
if attacker and (mission.return_to_station or mission.deliver_to_ship) then
mission.status = "FAILED"
end
if mission.destination == nil then
for i, e in pairs(mission.debris) do
if e.body ~= nil then
-- set next target
mission.destination = e.body
break
end
end
end
break
end
end
end
end
local onJettison = function (ship, cargo)
if not ship:IsPlayer() then return end
for ref, mission in pairs(missions) do
if mission.reward > 0 and not mission.warning then
for i, e in pairs(mission.debris) do
if cargo == e.cargo then
mission.warning = true
Comms.ImportantMessage(l.WARNING, mission.client.name)
break
end
end
end
end
end
local onShipHit = function (ship, attacker)
if ship:IsPlayer() then return end
if attacker == nil or not attacker:isa('Ship') then return end
for ref, mission in pairs(missions) do
if mission.police then
for _, s in pairs(mission.police) do
if s == ship then
ship:AIKill(attacker)
break
end
end
elseif mission.client_ship == ship then
ship:AIKill(attacker)
break
end
end
end
local onShipDestroyed = function (ship, attacker)
if ship:IsPlayer() then return end
for ref, mission in pairs(missions) do
if mission.police then
for i, s in pairs(mission.police) do
if s == ship then
table.remove(mission.police, i)
break
end
end
elseif mission.client_ship == ship then
mission.client_ship = nil
local msg = string.interp(l.SHIP_DESTROYED, {
shipid = mission.ship_label,
station = mission.station:GetSystemBody().name
})
Comms.ImportantMessage(msg, mission.client.name)
break
end
end
end
local onShipDocked = function (player, station)
if not player:IsPlayer() then return end
for ref, mission in pairs(missions) do
if mission.police then
for _, s in pairs(mission.police) do
if station.type == "STARPORT_SURFACE" then
s:AIEnterLowOrbit(Space.GetBody(station:GetSystemBody().parent.index))
else
s:AIFlyTo(station)
end
end
end
if mission.return_to_station or mission.deliver_to_ship and not mission.client_ship and mission.station == station.path then
-- unload mission cargo
for i, e in pairs(mission.debris) do
if e.body == nil then
if player:RemoveEquip(e.cargo, 1, "cargo") == 1 then
mission.debris[i] = nil
mission.amount = mission.amount - 1
end
end
end
if mission.amount == 0 then
mission.status = "COMPLETED"
elseif mission.destination == nil then
mission.status = "FAILED"
end
if mission.status == "COMPLETED" or mission.status == "FAILED" then
removeMission(mission, ref)
end
-- remove stale missions, if any
-- all cargo related to flavour 1 and 2 scooped or destroyed
elseif mission.reward < 0 and mission.destination == nil then
mission:Remove()
missions[ref] = nil
end
end
end
local onShipUndocked = function (player, station)
if not player:IsPlayer() then return end
for ref, mission in pairs(missions) do
if mission.police then
for _, s in pairs(mission.police) do
s:AIKill(player)
end
end
if mission.deliver_to_ship and not mission.in_progess then
mission.in_progress = true
transferCargo(mission, ref)
end
end
end
local getPopulatedPlanets = function (system)
local planets = {}
for _, p in ipairs(system:GetBodyPaths()) do
if p:GetSystemBody().population > 0 then
table.insert(planets, p)
end
end
return planets
end
local onEnterSystem = function (ship)
if not ship:IsPlayer() or Game.system.population == 0 then return end
local planets = getPopulatedPlanets(Game.system)
local num = Engine.rand:Integer(0, math.ceil(Game.system.population * Game.system.lawlessness))
flavours[LEGAL].cargo_type, flavours[ILLEGAL].cargo_type = sortGoods(Equipment.cargo)
-- spawn random cargo (legal or illegal goods)
for i = 1, num do
local planet = planets[Engine.rand:Integer(1, #planets)]
local radius = planet:GetSystemBody().radius
local flavour = flavours[Engine.rand:Integer(LEGAL, ILLEGAL)]
local debris = flavour.cargo_type
if #debris > 0 then
spawnDebris(debris, flavour.amount, planet, radius * 1.2, radius * 3.5, mission_time)
end
end
end
local onLeaveSystem = function (ship)
if ship:IsPlayer() then
for ref, mission in pairs(missions) do
mission.destination = nil
mission.police = nil
if mission.client_ship then
mission.client_ship = nil
mission.status = "FAILED"
end
for i, e in pairs(mission.debris) do
e.body = nil
end
end
planets = nil
flavours[LEGAL].cargo_type = nil
flavours[ILLEGAL].cargo_type = nil
end
end
local onReputationChanged = function (oldRep, oldKills, newRep, newKills)
for ref, ad in pairs(ads) do
local oldQualified = isQualifiedFor(oldRep, ad)
if isQualifiedFor(newRep, ad) ~= oldQualified then
Event.Queue("onAdvertChanged", ad.station, ref);
end
end
end
local buildMissionDescription = function(mission)
local ui = require 'pigui'
local desc = {}
desc.description = mission.introtext:interp({
client = mission.client.name,
shipid = mission.ship_label,
star = mission.star:GetSystemBody().name,
planet = mission.planet:GetSystemBody().name,
cash = Format.Money(math.abs(mission.reward), false)
})
desc.details = {
{ l.CLIENT, mission.client.name },
{ l.SPACEPORT, mission.station:GetSystemBody().name },
mission.client_ship and { l.SHIP, mission.client_ship.label } or false,
false,
{ l.DEADLINE, ui.Format.Date(mission.due) }
}
desc.client = mission.client
desc.location = mission.destination or nil
if mission.deliver_to_ship then
desc.returnLocation = mission.client_ship or mission.station
end
return desc
end
local loaded_data
local onGameStart = function ()
ads = {}
missions = {}
if loaded_data and loaded_data.ads then
for k, ad in pairs(loaded_data.ads) do
local ref = ad.station:AddAdvert({
description = ad.desc,
icon = ad.id == "RESCUE" and "searchrescue" or "haul",
onChat = onChat,
onDelete = onDelete,
isEnabled = isEnabled
})
ads[ref] = ad
end
missions = loaded_data.missions
loaded_data = nil
for ref, mission in pairs(missions) do
if mission.deliver_to_ship then
mission.in_progress = true
transferCargo(mission, ref)
end
end
end
end
local onGameEnd = function ()
planets = nil
flavours[LEGAL].cargo_type = nil
flavours[ILLEGAL].cargo_type = nil
end
local serialize = function ()
return { ads = ads, missions = missions }
end
local unserialize = function (data)
loaded_data = data
end
Event.Register("onCreateBB", onCreateBB)
Event.Register("onUpdateBB", onUpdateBB)
Event.Register("onShipEquipmentChanged", onShipEquipmentChanged)
Event.Register("onShipDocked", onShipDocked)
Event.Register("onShipUndocked", onShipUndocked)
Event.Register("onShipHit", onShipHit)
Event.Register("onShipDestroyed", onShipDestroyed)
Event.Register("onJettison", onJettison)
Event.Register("onCargoDestroyed", onCargoDestroyed)
Event.Register("onEnterSystem", onEnterSystem)
Event.Register("onLeaveSystem", onLeaveSystem)
Event.Register("onGameStart", onGameStart)
Event.Register("onGameEnd", onGameEnd)
Event.Register("onReputationChanged", onReputationChanged)
Mission.RegisterType("Scoop", l.SCOOP, buildMissionDescription)
Serializer:Register("Scoop", serialize, unserialize)

View File

@ -102,7 +102,7 @@ void CargoBody::TimeStepUpdate(const float timeStep)
if (m_hasSelfdestruct) {
m_selfdestructTimer -= timeStep;
if (m_selfdestructTimer <= 0) {
LuaEvent::Queue("onCargoDestroyed", this, NULL);
LuaEvent::Queue("onCargoDestroyed", this);
Pi::game->GetSpace()->KillBody(this);
SfxManager::Add(this, TYPE_EXPLOSION);
}
@ -114,10 +114,10 @@ bool CargoBody::OnDamage(Body *attacker, float kgDamage, const CollisionContact
{
m_hitpoints -= kgDamage * 0.001f;
if (m_hitpoints < 0) {
if (attacker && attacker->IsType(Object::BODY))
if (attacker && attacker->IsType(ObjectType::BODY))
LuaEvent::Queue("onCargoDestroyed", this, dynamic_cast<Body *>(attacker));
else
LuaEvent::Queue("onCargoDestroyed", this, NULL);
LuaEvent::Queue("onCargoDestroyed", this);
Pi::game->GetSpace()->KillBody(this);
SfxManager::Add(this, TYPE_EXPLOSION);
}
@ -131,7 +131,7 @@ bool CargoBody::OnCollision(Body *b, Uint32 flags, double relVel)
int cargoscoop_cap = 0;
static_cast<Ship *>(b)->Properties().Get("cargo_scoop_cap", cargoscoop_cap);
if (cargoscoop_cap > 0) {
LuaEvent::Queue("onCargoDestroyed", this, NULL);
LuaEvent::Queue("onCargoDestroyed", this);
return true;
}
}

View File

@ -16,7 +16,7 @@ class CargoBody : public DynamicBody {
public:
OBJDEF(CargoBody, DynamicBody, CARGOBODY);
CargoBody() = delete;
CargoBody(const LuaRef &cargo, float selfdestructTimer = 86400.0f); // default to 24 h lifetime
CargoBody(const LuaRef &cargo, float selfdestructTimer = 86400.0f); // default to 24 h lifetime
CargoBody(const char *modelName, const LuaRef &cargo, float selfdestructTimer = 86400.0f); // default to 24 h lifetime
CargoBody(const Json &jsonObj, Space *space);
LuaRef GetCargoType() const { return m_cargo; }

View File

@ -728,6 +728,20 @@ static bool _body_from_json(const Json &obj)
return push_body_to_lua(body);
}
static int l_body_get_velocity(lua_State *l)
{
Body *b = LuaObject<Body>::CheckFromLua(1);
LuaPush<vector3d>(l, b->GetVelocity());
return 1;
}
static int l_body_set_velocity(lua_State *l)
{
Body *b = LuaObject<Body>::CheckFromLua(1);
b->SetVelocity(LuaPull<vector3d>(l, 2));
return 0;
}
template <>
const char *LuaObject<Body>::s_type = "Body";
@ -758,6 +772,8 @@ void LuaObject<Body>::RegisterClass()
{ "IsGroundStation", l_body_is_ground_station },
{ "IsCargoContainer", l_body_is_cargo_container },
{ "GetSystemBody", l_body_get_system_body },
{ "GetVelocity", l_body_get_velocity },
{ "SetVelocity", l_body_set_velocity },
{ 0, 0 }
};

View File

@ -1891,7 +1891,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l)
for (Body *body : Pi::game->GetSpace()->GetBodies()) {
if (body == Pi::game->GetPlayer()) continue;
if (body->GetType() == ObjectType::PROJECTILE) continue;
if (body->GetType() == ObjectType::SHIP &&
if ((body->GetType() == ObjectType::SHIP || body->GetType() == ObjectType::CARGOBODY) &&
body->GetPositionRelTo(Pi::player).Length() > ship_max_distance) continue;
const PiGui::TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp
if (!res._onScreen) continue;

View File

@ -2,6 +2,7 @@
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "LuaSpace.h"
#include "CargoBody.h"
#include "Frame.h"
#include "Game.h"
#include "HyperspaceCloud.h"
@ -16,7 +17,6 @@
#include "Ship.h"
#include "Space.h"
#include "SpaceStation.h"
#include "CargoBody.h"
/*
* Interface: Space
@ -361,7 +361,7 @@ static int l_space_spawn_ship_parked(lua_State *l)
Ship *ship = new Ship(type);
assert(ship);
const double parkDist = station->GetStationType()->ParkingDistance() - ship->GetPhysRadius(); // park inside parking radius
const double parkDist = station->GetStationType()->ParkingDistance() - ship->GetPhysRadius(); // park inside parking radius
const double parkOffset = (0.5 * station->GetStationType()->ParkingGapSize()) + ship->GetPhysRadius(); // but outside the docking gap
double xpos = (slot == 0 || slot == 3) ? -parkOffset : parkOffset;
@ -551,6 +551,27 @@ static int l_space_spawn_ship_landed_near(lua_State *l)
return 1;
}
// sb - central systembody, pos - absolute coordinates of given object
static vector3d _orbital_velocity_random_direction(const SystemBody *sb, const vector3d &pos)
{
// If we got a zero mass of central body - there is no orbit
if (sb->GetMass() < 0.01)
return vector3d(0.0);
// calculating basis from radius - vector
vector3d k = pos.Normalized();
vector3d i;
if (std::fabs(k.z) > 0.999999) // very vertical = z
i = vector3d(1.0, 0.0, 0.0); // second ort = x
else
i = k.Cross(vector3d(0.0, 0.0, 1.0)).Normalized();
vector3d j = k.Cross(i);
// generating random 2d direction and putting it into basis
vector3d randomOrthoDirection = MathUtil::RandomPointOnCircle(1.0) * matrix3x3d::FromVectors(i, j, k).Transpose();
// calculate the value of the orbital velocity
double orbitalVelocity = sqrt(G * sb->GetMass() / pos.Length());
return randomOrthoDirection * orbitalVelocity;
}
/*
* Function: SpawnCargoNear
*
@ -585,8 +606,8 @@ static int l_space_spawn_cargo_near(lua_State *l)
LUA_DEBUG_START(l);
CargoBody * c_body;
const char * model;
CargoBody *c_body;
const char *model;
lua_getfield(l, 1, "model_name");
if (lua_isstring(l, -1))
@ -594,21 +615,29 @@ static int l_space_spawn_cargo_near(lua_State *l)
else
model = "cargo";
if (lua_gettop(l) >= 5){
if (lua_gettop(l) >= 5) {
float lifetime = lua_tonumber(l, 5);
c_body = new CargoBody(model, LuaRef(l, 1), lifetime);
} else {
c_body = new CargoBody(model, LuaRef(l, 1));
}
Body * nearbody = LuaObject<Body>::CheckFromLua(2);
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
float min_dist = luaL_checknumber(l, 3);
float max_dist = luaL_checknumber(l, 4);
if (min_dist > max_dist)
luaL_error(l, "min_dist must not be larger than max_dist");
c_body->SetFrame(nearbody->GetFrame());
c_body->SetPosition((MathUtil::RandomPointOnSphere(min_dist, max_dist)) + nearbody->GetPosition());
c_body->SetVelocity(vector3d(0,0,0));
FrameId frameId = nearbody->GetFrame();
Frame *frame = Frame::GetFrame(frameId);
// if the frame is rotating, use non-rotating parent
if (frame->IsRotFrame()) {
assert(frame->GetParent());
frame = Frame::GetFrame(frame->GetParent());
frameId = frame->GetId();
}
c_body->SetFrame(frameId);
c_body->SetPosition(MathUtil::RandomPointOnSphere(min_dist, max_dist) + nearbody->GetPosition());
c_body->SetVelocity(_orbital_velocity_random_direction(frame->GetSystemBody(), c_body->GetPosition()));
Pi::game->GetSpace()->AddBody(c_body);
LuaObject<Body>::PushToLua(c_body);
@ -618,6 +647,72 @@ static int l_space_spawn_cargo_near(lua_State *l)
return 1;
}
/*
* Function: SpawnShipOrbit
*
* Create a ship and place it in orbit near the given <Body>.
*
* > ship = Space.SpawnShip(type, body, min, max)
*
* Parameters:
*
* type - the name of the ship
*
* body - the <Body> near which the ship should be spawned
*
* min - minimum distance from the body to place the ship, in m
*
* max - maximum distance to place the ship
*
*
* Return:
*
* ship - a <Ship> object for the new ship
*
* Status:
*
* experimental
*/
static int l_space_spawn_ship_orbit(lua_State *l)
{
if (!Pi::game)
luaL_error(l, "Game is not started");
LUA_DEBUG_START(l);
const char *type = luaL_checkstring(l, 1);
if (!ShipType::Get(type))
luaL_error(l, "Unknown ship type '%s'", type);
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
float min_dist = luaL_checknumber(l, 3);
float max_dist = luaL_checknumber(l, 4);
if (min_dist > max_dist)
luaL_error(l, "min_dist must not be larger than max_dist");
Ship *ship = new Ship(type);
assert(ship);
FrameId frameId = nearbody->GetFrame();
Frame *frame = Frame::GetFrame(frameId);
// if the frame is rotating, use non-rotating parent
if (frame->IsRotFrame()) {
assert(frame->GetParent());
frame = Frame::GetFrame(frame->GetParent());
frameId = frame->GetId();
}
ship->SetFrame(frameId);
ship->SetPosition(MathUtil::RandomPointOnSphere(min_dist, max_dist) + nearbody->GetPosition());
ship->SetVelocity(_orbital_velocity_random_direction(frame->GetSystemBody(), ship->GetPosition()));
Pi::game->GetSpace()->AddBody(ship);
LuaObject<Ship>::PushToLua(ship);
LUA_DEBUG_END(l, 1);
return 1;
}
/*
* Function: GetBody
*
@ -789,11 +884,11 @@ void LuaSpace::Register()
{ "SpawnShipLanded", l_space_spawn_ship_landed },
{ "SpawnShipLandedNear", l_space_spawn_ship_landed_near },
{ "SpawnCargoNear", l_space_spawn_cargo_near },
{ "SpawnShipOrbit", l_space_spawn_ship_orbit },
{ "GetBody", l_space_get_body },
{ "GetBodies", l_space_get_bodies },
{ "DbgDumpFrames", l_space_dump_frames },
{ 0, 0 }
};