Merge branch 'scoop_mission'
commit
55c530e025
|
@ -1,13 +1,19 @@
|
||||||
January 2021
|
January 2021
|
||||||
|
* New Features
|
||||||
|
* New scoop mission (#4860)
|
||||||
|
|
||||||
* Internal Changes
|
* Internal Changes
|
||||||
* Happy new year (#5105)
|
* Happy new year (#5105)
|
||||||
* Update buildscripts to use Ubuntu 20.04 (#5109)
|
* Update buildscripts to use Ubuntu 20.04 (#5109)
|
||||||
|
* Fix build with system lua (#5121)
|
||||||
|
|
||||||
* Fixes
|
* Fixes
|
||||||
* Fix surface sounds playing after leaving surface (#5115)
|
* Fix surface sounds playing after leaving surface (#5115)
|
||||||
* Fix segfault when setting destination to binary star (#5114)
|
* Fix segfault when setting destination to binary star (#5114)
|
||||||
* Fix background display issues when changing star density slider (#5116)
|
* Fix background display issues when changing star density slider (#5116)
|
||||||
* Fix crash triggered by spacestation when removing advert (#5118)
|
* Fix crash triggered by spacestation when removing advert (#5118)
|
||||||
|
* Fix empty landing pad being occupied by a skipjack ship (#5086)
|
||||||
|
* Fix game crash when selecting system map in hyperspace (#5120)
|
||||||
|
|
||||||
December 2020
|
December 2020
|
||||||
* New Features
|
* New Features
|
||||||
|
|
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Sfx.h"
|
#include "Sfx.h"
|
||||||
#include "Ship.h"
|
#include "Ship.h"
|
||||||
#include "Space.h"
|
#include "Space.h"
|
||||||
|
#include "lua/LuaEvent.h"
|
||||||
|
|
||||||
CargoBody::CargoBody(const LuaRef &cargo, float selfdestructTimer) :
|
CargoBody::CargoBody(const LuaRef &cargo, float selfdestructTimer) :
|
||||||
m_cargo(cargo)
|
m_cargo(cargo)
|
||||||
|
@ -22,6 +23,18 @@ CargoBody::CargoBody(const LuaRef &cargo, float selfdestructTimer) :
|
||||||
m_hasSelfdestruct = false;
|
m_hasSelfdestruct = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CargoBody::CargoBody(const char *modelName, const LuaRef &cargo, float selfdestructTimer) :
|
||||||
|
m_cargo(cargo)
|
||||||
|
{
|
||||||
|
SetModel(modelName);
|
||||||
|
Init();
|
||||||
|
SetMass(1.0);
|
||||||
|
m_selfdestructTimer = selfdestructTimer; // number of seconds to live
|
||||||
|
|
||||||
|
if (is_zero_exact(selfdestructTimer)) // turn off self destruct
|
||||||
|
m_hasSelfdestruct = false;
|
||||||
|
}
|
||||||
|
|
||||||
CargoBody::CargoBody(const Json &jsonObj, Space *space) :
|
CargoBody::CargoBody(const Json &jsonObj, Space *space) :
|
||||||
DynamicBody(jsonObj, space)
|
DynamicBody(jsonObj, space)
|
||||||
{
|
{
|
||||||
|
@ -89,6 +102,7 @@ void CargoBody::TimeStepUpdate(const float timeStep)
|
||||||
if (m_hasSelfdestruct) {
|
if (m_hasSelfdestruct) {
|
||||||
m_selfdestructTimer -= timeStep;
|
m_selfdestructTimer -= timeStep;
|
||||||
if (m_selfdestructTimer <= 0) {
|
if (m_selfdestructTimer <= 0) {
|
||||||
|
LuaEvent::Queue("onCargoDestroyed", this);
|
||||||
Pi::game->GetSpace()->KillBody(this);
|
Pi::game->GetSpace()->KillBody(this);
|
||||||
SfxManager::Add(this, TYPE_EXPLOSION);
|
SfxManager::Add(this, TYPE_EXPLOSION);
|
||||||
}
|
}
|
||||||
|
@ -100,6 +114,10 @@ bool CargoBody::OnDamage(Body *attacker, float kgDamage, const CollisionContact
|
||||||
{
|
{
|
||||||
m_hitpoints -= kgDamage * 0.001f;
|
m_hitpoints -= kgDamage * 0.001f;
|
||||||
if (m_hitpoints < 0) {
|
if (m_hitpoints < 0) {
|
||||||
|
if (attacker && attacker->IsType(ObjectType::BODY))
|
||||||
|
LuaEvent::Queue("onCargoDestroyed", this, dynamic_cast<Body *>(attacker));
|
||||||
|
else
|
||||||
|
LuaEvent::Queue("onCargoDestroyed", this);
|
||||||
Pi::game->GetSpace()->KillBody(this);
|
Pi::game->GetSpace()->KillBody(this);
|
||||||
SfxManager::Add(this, TYPE_EXPLOSION);
|
SfxManager::Add(this, TYPE_EXPLOSION);
|
||||||
}
|
}
|
||||||
|
@ -112,8 +130,10 @@ bool CargoBody::OnCollision(Body *b, Uint32 flags, double relVel)
|
||||||
if (b->IsType(ObjectType::SHIP)) {
|
if (b->IsType(ObjectType::SHIP)) {
|
||||||
int cargoscoop_cap = 0;
|
int cargoscoop_cap = 0;
|
||||||
static_cast<Ship *>(b)->Properties().Get("cargo_scoop_cap", cargoscoop_cap);
|
static_cast<Ship *>(b)->Properties().Get("cargo_scoop_cap", cargoscoop_cap);
|
||||||
if (cargoscoop_cap > 0)
|
if (cargoscoop_cap > 0) {
|
||||||
|
LuaEvent::Queue("onCargoDestroyed", this);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DynamicBody::OnCollision(b, flags, relVel);
|
return DynamicBody::OnCollision(b, flags, relVel);
|
||||||
|
|
|
@ -16,7 +16,8 @@ class CargoBody : public DynamicBody {
|
||||||
public:
|
public:
|
||||||
OBJDEF(CargoBody, DynamicBody, CARGOBODY);
|
OBJDEF(CargoBody, DynamicBody, CARGOBODY);
|
||||||
CargoBody() = delete;
|
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);
|
CargoBody(const Json &jsonObj, Space *space);
|
||||||
LuaRef GetCargoType() const { return m_cargo; }
|
LuaRef GetCargoType() const { return m_cargo; }
|
||||||
virtual void SetLabel(const std::string &label) override;
|
virtual void SetLabel(const std::string &label) override;
|
||||||
|
|
|
@ -728,6 +728,20 @@ static bool _body_from_json(const Json &obj)
|
||||||
return push_body_to_lua(body);
|
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 <>
|
template <>
|
||||||
const char *LuaObject<Body>::s_type = "Body";
|
const char *LuaObject<Body>::s_type = "Body";
|
||||||
|
|
||||||
|
@ -758,6 +772,8 @@ void LuaObject<Body>::RegisterClass()
|
||||||
{ "IsGroundStation", l_body_is_ground_station },
|
{ "IsGroundStation", l_body_is_ground_station },
|
||||||
{ "IsCargoContainer", l_body_is_cargo_container },
|
{ "IsCargoContainer", l_body_is_cargo_container },
|
||||||
{ "GetSystemBody", l_body_get_system_body },
|
{ "GetSystemBody", l_body_get_system_body },
|
||||||
|
{ "GetVelocity", l_body_get_velocity },
|
||||||
|
{ "SetVelocity", l_body_set_velocity },
|
||||||
{ 0, 0 }
|
{ 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1891,7 +1891,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l)
|
||||||
for (Body *body : Pi::game->GetSpace()->GetBodies()) {
|
for (Body *body : Pi::game->GetSpace()->GetBodies()) {
|
||||||
if (body == Pi::game->GetPlayer()) continue;
|
if (body == Pi::game->GetPlayer()) continue;
|
||||||
if (body->GetType() == ObjectType::PROJECTILE) 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;
|
body->GetPositionRelTo(Pi::player).Length() > ship_max_distance) continue;
|
||||||
const PiGui::TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp
|
const PiGui::TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp
|
||||||
if (!res._onScreen) continue;
|
if (!res._onScreen) continue;
|
||||||
|
|
|
@ -462,12 +462,19 @@ static int l_ship_spawn_cargo(lua_State *l)
|
||||||
}
|
}
|
||||||
|
|
||||||
CargoBody *c_body;
|
CargoBody *c_body;
|
||||||
|
const char *model;
|
||||||
|
|
||||||
|
lua_getfield(l, 2, "model_name");
|
||||||
|
if (lua_isstring(l, -1))
|
||||||
|
model = lua_tostring(l, -1);
|
||||||
|
else
|
||||||
|
model = "cargo";
|
||||||
|
|
||||||
if (lua_gettop(l) >= 3) {
|
if (lua_gettop(l) >= 3) {
|
||||||
float lifeTime = lua_tonumber(l, 3);
|
float lifeTime = lua_tonumber(l, 3);
|
||||||
c_body = new CargoBody(LuaRef(l, 2), lifeTime);
|
c_body = new CargoBody(model, LuaRef(l, 2), lifeTime);
|
||||||
} else
|
} else
|
||||||
c_body = new CargoBody(LuaRef(l, 2));
|
c_body = new CargoBody(model, LuaRef(l, 2));
|
||||||
|
|
||||||
lua_pushboolean(l, s->SpawnCargo(c_body));
|
lua_pushboolean(l, s->SpawnCargo(c_body));
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
||||||
|
|
||||||
#include "LuaSpace.h"
|
#include "LuaSpace.h"
|
||||||
|
#include "CargoBody.h"
|
||||||
#include "Frame.h"
|
#include "Frame.h"
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "HyperspaceCloud.h"
|
#include "HyperspaceCloud.h"
|
||||||
|
@ -360,7 +361,7 @@ static int l_space_spawn_ship_parked(lua_State *l)
|
||||||
Ship *ship = new Ship(type);
|
Ship *ship = new Ship(type);
|
||||||
assert(ship);
|
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
|
const double parkOffset = (0.5 * station->GetStationType()->ParkingGapSize()) + ship->GetPhysRadius(); // but outside the docking gap
|
||||||
|
|
||||||
double xpos = (slot == 0 || slot == 3) ? -parkOffset : parkOffset;
|
double xpos = (slot == 0 || slot == 3) ? -parkOffset : parkOffset;
|
||||||
|
@ -550,6 +551,168 @@ static int l_space_spawn_ship_landed_near(lua_State *l)
|
||||||
return 1;
|
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
|
||||||
|
*
|
||||||
|
* Create a cargo container and place near the given <Body>
|
||||||
|
*
|
||||||
|
* > body = Space.SpawnCargoNear(item, body, min, max, lifetime)
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* item - the item to put into the container
|
||||||
|
*
|
||||||
|
* body - the <Body> near which the container should be spawned
|
||||||
|
*
|
||||||
|
* min - minimum distance from the body to place the container, in m
|
||||||
|
*
|
||||||
|
* max - maximum distance to place the container
|
||||||
|
*
|
||||||
|
* lifetime - optional time in seconds until self destruct, default is 24h
|
||||||
|
*
|
||||||
|
* Return:
|
||||||
|
*
|
||||||
|
* body - the <Body> object for the requested container
|
||||||
|
*
|
||||||
|
* Status:
|
||||||
|
*
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
static int l_space_spawn_cargo_near(lua_State *l)
|
||||||
|
{
|
||||||
|
if (!Pi::game)
|
||||||
|
luaL_error(l, "Game is not started");
|
||||||
|
|
||||||
|
LUA_DEBUG_START(l);
|
||||||
|
|
||||||
|
CargoBody *c_body;
|
||||||
|
const char *model;
|
||||||
|
|
||||||
|
lua_getfield(l, 1, "model_name");
|
||||||
|
if (lua_isstring(l, -1))
|
||||||
|
model = lua_tostring(l, -1);
|
||||||
|
else
|
||||||
|
model = "cargo";
|
||||||
|
|
||||||
|
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);
|
||||||
|
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");
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
LUA_DEBUG_END(l, 1);
|
||||||
|
|
||||||
|
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
|
* Function: GetBody
|
||||||
*
|
*
|
||||||
|
@ -720,11 +883,12 @@ void LuaSpace::Register()
|
||||||
{ "SpawnShipParked", l_space_spawn_ship_parked },
|
{ "SpawnShipParked", l_space_spawn_ship_parked },
|
||||||
{ "SpawnShipLanded", l_space_spawn_ship_landed },
|
{ "SpawnShipLanded", l_space_spawn_ship_landed },
|
||||||
{ "SpawnShipLandedNear", l_space_spawn_ship_landed_near },
|
{ "SpawnShipLandedNear", l_space_spawn_ship_landed_near },
|
||||||
|
{ "SpawnCargoNear", l_space_spawn_cargo_near },
|
||||||
|
{ "SpawnShipOrbit", l_space_spawn_ship_orbit },
|
||||||
|
|
||||||
{ "GetBody", l_space_get_body },
|
{ "GetBody", l_space_get_body },
|
||||||
{ "GetBodies", l_space_get_bodies },
|
{ "GetBodies", l_space_get_bodies },
|
||||||
|
|
||||||
|
|
||||||
{ "DbgDumpFrames", l_space_dump_frames },
|
{ "DbgDumpFrames", l_space_dump_frames },
|
||||||
{ 0, 0 }
|
{ 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue