1-- Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details 2-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt 3 4local Game = require 'Game' 5local Lang = require 'Lang' 6local Ship = require 'Ship' 7local Comms = require 'Comms' 8local Event = require 'Event' 9local Space = require 'Space' 10local Timer = require 'Timer' 11local Engine = require 'Engine' 12local Format = require 'Format' 13local Mission = require 'Mission' 14local ShipDef = require 'ShipDef' 15local Character = require 'Character' 16local Equipment = require 'Equipment' 17local Serializer = require 'Serializer' 18 19local utils = require 'utils' 20 21local l = Lang.GetResource("module-scoop") 22local lc = Lang.GetResource("ui-core") 23 24local AU = 149597870700.0 25local LEGAL = 1 26local ILLEGAL = 2 27 28local mission_reputation = 1 29local mission_time = 14*24*60*60 30local max_dist = 20 * AU 31 32local ads = {} 33local missions = {} 34 35local rescue_capsule = Equipment.EquipType.New({ 36 name = "rescue_capsule", 37 l10n_key = "RESCUE_CAPSULE", 38 l10n_resource = "module-scoop", 39 slots = "cargo", 40 price = 500, 41 icon_name = "Default", 42 model_name = "escape_pod", 43 capabilities = { mass = 1, crew = 1 }, 44 purchasable = false 45}) 46 47local rocket_launchers = Equipment.EquipType.New({ 48 name = "rocket_launchers", 49 l10n_key = "ROCKET_LAUNCHERS", 50 l10n_resource = "module-scoop", 51 slots = "cargo", 52 price = 500, 53 icon_name = "Default", 54 capabilities = { mass = 1 }, 55 purchasable = false 56}) 57 58local detonators = Equipment.EquipType.New({ 59 name = "detonators", 60 l10n_key = "DETONATORS", 61 l10n_resource = "module-scoop", 62 slots = "cargo", 63 price = 250, 64 icon_name = "Default", 65 capabilities = { mass = 1 }, 66 purchasable = false 67}) 68 69local nuclear_missile = Equipment.EquipType.New({ 70 name = "nuclear_missile", 71 l10n_key = "NUCLEAR_MISSILE", 72 l10n_resource = "module-scoop", 73 slots = "cargo", 74 price = 1250, 75 icon_name = "Default", 76 model_name = "missile", 77 capabilities = { mass = 1 }, 78 purchasable = false 79}) 80 81-- Useless waste that the player has to sort out 82local toxic_waste = Equipment.EquipType.New({ 83 name = "toxic_waste", 84 l10n_key = "TOXIC_WASTE", 85 l10n_resource = "module-scoop", 86 slots = "cargo", 87 price = -50, 88 icon_name = "Default", 89 capabilities = { mass = 1 }, 90 purchasable = false 91}) 92 93local spoiled_food = Equipment.EquipType.New({ 94 name = "spoiled_food", 95 l10n_key = "SPOILED_FOOD", 96 l10n_resource = "module-scoop", 97 slots = "cargo", 98 price = -10, 99 icon_name = "Default", 100 capabilities = { mass = 1 }, 101 purchasable = false 102}) 103 104local unknown = Equipment.EquipType.New({ 105 name = "unknown", 106 l10n_key = "UNKNOWN", 107 l10n_resource = "module-scoop", 108 slots = "cargo", 109 price = -5, 110 icon_name = "Default", 111 capabilities = { mass = 1 }, 112 purchasable = false 113}) 114 115local rescue_capsules = { 116 rescue_capsule 117} 118 119local weapons = { 120 rocket_launchers, 121 detonators, 122 nuclear_missile 123} 124 125local waste = { 126 toxic_waste, 127 spoiled_food, 128 unknown, 129 Equipment.cargo.radioactives, 130 Equipment.cargo.rubbish 131} 132 133local flavours = { 134 { 135 id = "LEGAL_GOODS", 136 cargo_type = nil, 137 reward = -500, 138 amount = 20, 139 }, 140 { 141 id = "ILLEGAL_GOODS", 142 cargo_type = nil, 143 reward = -1000, 144 amount = 10, 145 }, 146 { 147 id = "RESCUE", 148 cargo_type = rescue_capsules, 149 reward = 750, 150 amount = 4, 151 return_to_station = true, 152 }, 153 { 154 id = "ARMS_DEALER", 155 cargo_type = weapons, 156 reward = 1000, 157 amount = 1, 158 deliver_to_ship = true, 159 }, 160} 161 162-- Sort goods, legal and illegal 163local sortGoods = function (goods) 164 local legal_goods = {} 165 local illegal_goods = {} 166 local system = Game.system 167 168 for _, e in pairs(goods) do 169 if e.purchasable and system:IsCommodityLegal(e.name) then 170 table.insert(legal_goods, e) 171 else 172 table.insert(illegal_goods, e) 173 end 174 end 175 176 return legal_goods, illegal_goods 177end 178 179-- Returns the number of flavours of the given string (assuming first flavour has suffix '_1'). 180local getNumberOfFlavours = function (str) 181 local num = 1 182 183 while l:get(str .. "_" .. num) do 184 num = num + 1 185 end 186 187 return num - 1 188end 189 190-- Create a debris field in a random distance to a system body 191local spawnDebris = function (debris, amount, sbody, min, max, lifetime) 192 local list = {} 193 local cargo 194 local body = Space.GetBody(sbody:GetSystemBody().index) 195 196 for i = 1, Engine.rand:Integer(math.ceil(amount / 4), amount) do 197 cargo = debris[Engine.rand:Integer(1, #debris)] 198 body = Space.SpawnCargoNear(cargo, body, min, max, lifetime) 199 if i > 1 then body:SetVelocity(list[1].body:GetVelocity()) end 200 table.insert(list, { cargo = cargo, body = body }) 201 min = 10 202 max = 1000 203 end 204 205 -- add some useless waste 206 for i = 1, Engine.rand:Integer(1, 9) do 207 cargo = waste[Engine.rand:Integer(1, #waste)] 208 body = Space.SpawnCargoNear(cargo, body, min, max, lifetime) 209 body:SetVelocity(list[1].body:GetVelocity()) 210 end 211 212 return list 213end 214 215-- Create a couple of police ships 216local spawnPolice = function (station) 217 local ship 218 local police = {} 219 local shipdef = ShipDef[Game.system.faction.policeShip] 220 221 for i = 1, 2 do 222 ship = Space.SpawnShipDocked(shipdef.id, station) 223 ship:SetLabel(lc.POLICE) 224 ship:AddEquip(Equipment.laser.pulsecannon_1mw) 225 table.insert(police, ship) 226 if station.type == "STARPORT_SURFACE" then 227 ship:AIEnterLowOrbit(Space.GetBody(station:GetSystemBody().parent.index)) 228 end 229 end 230 231 Timer:CallAt(Game.time + 5, function () 232 for _, s in pairs(police) do 233 s:AIKill(Game.player) 234 end 235 end) 236 237 return police 238end 239 240-- Returns a random system close to the players location 241local nearbySystem = function () 242 local dist = 5 243 local systems = {} 244 245 while #systems < 1 do 246 systems = Game.system:GetNearbySystems(dist) 247 dist = dist + 5 248 end 249 250 return systems[Engine.rand:Integer(1, #systems)].path 251end 252 253-- Create a ship in orbit 254local spawnClientShip = function (star, ship_label) 255 local shipdefs = utils.build_array(utils.filter( 256 function (k, def) 257 return def.tag == "SHIP" and def.hyperdriveClass > 0 and def.equipSlotCapacity["scoop"] > 0 258 end, 259 pairs(ShipDef))) 260 local shipdef = shipdefs[Engine.rand:Integer(1, #shipdefs)] 261 262 local radius = star:GetSystemBody().radius 263 local min, max 264 if star:GetSystemBody().type == "WHITE_DWARF" then 265 min = radius * 30 266 max = radius * 40 267 else 268 min = radius * 3.5 269 max = radius * 4.5 270 end 271 272 local ship = Space.SpawnShipOrbit(shipdef.id, Space.GetBody(star:GetSystemBody().index), min, max) 273 274 ship:SetLabel(ship_label) 275 ship:AddEquip(Equipment.hyperspace["hyperdrive_" .. shipdef.hyperdriveClass]) 276 ship:AddEquip(Equipment.laser.pulsecannon_2mw) 277 ship:AddEquip(Equipment.misc.shield_generator) 278 279 return ship 280end 281 282local removeMission = function (mission, ref) 283 local oldReputation = Character.persistent.player.reputation 284 local sender = mission.client_ship and mission.ship_label or mission.client.name 285 286 if mission.status == "COMPLETED" then 287 Character.persistent.player.reputation = oldReputation + mission_reputation 288 Game.player:AddMoney(mission.reward) 289 Comms.ImportantMessage(l["SUCCESS_MSG_" .. mission.id], sender) 290 elseif mission.status == "FAILED" then 291 Character.persistent.player.reputation = oldReputation - mission_reputation 292 Comms.ImportantMessage(l["FAILURE_MSG_" .. mission.id], sender) 293 end 294 Event.Queue("onReputationChanged", oldReputation, Character.persistent.player.killcount, 295 Character.persistent.player.reputation, Character.persistent.player.killcount) 296 297 if ref == nil then 298 for r, m in pairs(missions) do 299 if m == mission then ref = r break end 300 end 301 end 302 mission:Remove() 303 missions[ref] = nil 304end 305 306-- Cargo transfer to a ship 307local transferCargo = function (mission, ref) 308 Timer:CallEvery(9, function () 309 if not mission.client_ship then return true end 310 311 if not mission.docking_in_progress and Game.player:DistanceTo(mission.client_ship) <= 5000 then 312 mission.docking_in_progress = true 313 Comms.ImportantMessage(l.DOCKING_INSTRUCTION, mission.ship_label) 314 end 315 316 if Game.player:DistanceTo(mission.client_ship) <= 100 then 317 318 -- unload mission cargo 319 for i, e in pairs(mission.debris) do 320 if e.body == nil then 321 if Game.player:RemoveEquip(e.cargo, 1, "cargo") == 1 then 322 mission.client_ship:AddEquip(e.cargo, 1, "cargo") 323 mission.debris[i] = nil 324 mission.amount = mission.amount - 1 325 end 326 end 327 end 328 329 if mission.amount == 0 then 330 mission.status = "COMPLETED" 331 elseif mission.destination == nil then 332 mission.status = "FAILED" 333 end 334 end 335 336 if mission.status == "COMPLETED" or mission.status == "FAILED" then 337 local ship = mission.client_ship 338 mission.client_ship = nil 339 removeMission(mission, ref) 340 ship:HyperjumpTo(nearbySystem()) 341 end 342 end) 343end 344 345local isQualifiedFor = function(reputation, ad) 346 return reputation > (ad.reward/100) or false 347end 348 349local onDelete = function (ref) 350 ads[ref] = nil 351end 352 353local isEnabled = function (ref) 354 return ads[ref] ~= nil and isQualifiedFor(Character.persistent.player.reputation, ads[ref]) 355end 356 357local onChat = function (form, ref, option) 358 local ad = ads[ref] 359 local player = Game.player 360 local debris, ship, radius, mindist, maxdist 361 362 form:Clear() 363 364 if option == -1 then 365 form:Close() 366 return 367 end 368 369 local qualified = isQualifiedFor(Character.persistent.player.reputation, ad) 370 371 form:SetFace(ad.client) 372 373 if not qualified then 374 form:SetMessage(l["DENY_" .. Engine.rand:Integer(1, getNumberOfFlavours("DENY"))]) 375 return 376 end 377 378 form:AddNavButton(ad.planet) 379 380 if option == 0 then 381 local introtext = string.interp(ad.introtext, { 382 client = ad.client.name, 383 shipid = ad.ship_label, 384 star = ad.star:GetSystemBody().name, 385 planet = ad.planet:GetSystemBody().name, 386 cash = Format.Money(math.abs(ad.reward), false), 387 }) 388 form:SetMessage(introtext) 389 390 elseif option == 1 then 391 form:SetMessage(l["WHY_NOT_YOURSELF_" .. ad.id]) 392 393 elseif option == 2 then 394 form:SetMessage(string.interp(l["HOW_MUCH_TIME_" .. ad.id], { star = ad.star:GetSystemBody().name, date = Format.Date(ad.due) })) 395 396 elseif option == 3 then 397 if ad.reward > 0 and player:CountEquip(Equipment.misc.cargo_scoop) == 0 and player:CountEquip(Equipment.misc.multi_scoop) == 0 then 398 form:SetMessage(l.YOU_DO_NOT_HAVE_A_SCOOP) 399 form:RemoveNavButton() 400 return 401 end 402 403 if ad.reward < 0 and player:GetMoney() < math.abs(ad.reward) then 404 form:SetMessage(l.YOU_DO_NOT_HAVE_ENOUGH_MONEY) 405 form:RemoveNavButton() 406 return 407 end 408 409 form:RemoveAdvertOnClose() 410 ads[ref] = nil 411 412 radius = ad.planet:GetSystemBody().radius 413 if ad.planet:GetSystemBody().superType == "ROCKY_PLANET" then 414 mindist = radius * 2.5 415 maxdist = radius * 3.5 416 else 417 mindist = radius * 25 418 maxdist = radius * 35 419 end 420 debris = spawnDebris(ad.debris_type, ad.amount, ad.planet, mindist, maxdist, ad.due - Game.time) 421 422 if ad.reward < 0 then player:AddMoney(ad.reward) end 423 if ad.deliver_to_ship then 424 ship = spawnClientShip(ad.star, ad.ship_label) 425 end 426 427 local mission = { 428 type = "Scoop", 429 location = ad.location, 430 introtext = ad.introtext, 431 client = ad.client, 432 station = ad.station.path, 433 star = ad.star, 434 planet = ad.planet, 435 id = ad.id, 436 debris = debris, 437 amount = #debris, 438 reward = ad.reward, 439 due = ad.due, 440 return_to_station = ad.return_to_station, 441 deliver_to_ship = ad.deliver_to_ship, 442 client_ship = ship, 443 ship_label = ad.ship_label, 444 destination = debris[1].body 445 } 446 447 table.insert(missions, Mission.New(mission)) 448 form:SetMessage(l["ACCEPTED_" .. ad.id]) 449 form:RemoveNavButton() 450 form:AddNavButton(debris[1].body) 451 return 452 end 453 454 form:AddOption(l.WHY_NOT_YOURSELF, 1) 455 form:AddOption(l.HOW_MUCH_TIME, 2) 456 form:AddOption(l.REPEAT_THE_REQUEST, 0) 457 form:AddOption(l.OK_AGREED, 3) 458end 459 460local getPlanets = function (system) 461 local planets = {} 462 463 for _, p in ipairs(system:GetBodyPaths()) do 464 if p:GetSystemBody().superType == "ROCKY_PLANET" or p:GetSystemBody().superType == "GAS_GIANT" then 465 table.insert(planets, p) 466 end 467 end 468 return planets 469end 470 471local planets = nil 472 473local makeAdvert = function (station) 474 if planets == nil then planets = getPlanets(Game.system) end 475 if #planets == 0 then return end 476 477 if flavours[LEGAL].cargo_type == nil then 478 flavours[LEGAL].cargo_type, flavours[ILLEGAL].cargo_type = sortGoods(Equipment.cargo) 479 end 480 481 local stars = Game.system:GetStars() 482 local star = stars[Engine.rand:Integer(1, #stars)] 483 local planet = planets[Engine.rand:Integer(1, #planets)] 484 local dist = station:DistanceTo(Space.GetBody(planet:GetSystemBody().index)) 485 local flavour = flavours[Engine.rand:Integer(1, #flavours)] 486 local due = Game.time + mission_time * (1 + dist / max_dist) * Engine.rand:Number(0.8, 1.2) 487 local reward 488 489 if flavour.reward < 0 then 490 reward = flavour.reward * (1.15 - dist / max_dist) * Engine.rand:Number(0.9, 1.1) 491 else 492 reward = flavour.reward * (1 + dist / max_dist) * Engine.rand:Number(0.75, 1.25) 493 end 494 reward = utils.round(reward, 50) 495 496 if #flavour.cargo_type > 0 and dist < max_dist and station:DistanceTo(Space.GetBody(star.index)) < max_dist then 497 local ad = { 498 station = station, 499 location = planet, 500 introtext = l["INTROTEXT_" .. flavour.id], 501 client = Character.New(), 502 star = star.path, 503 planet = planet, 504 id = flavour.id, 505 debris_type = flavour.cargo_type, 506 reward = math.ceil(reward), 507 amount = flavour.amount, 508 due = due, 509 return_to_station = flavour.return_to_station, 510 deliver_to_ship = flavour.deliver_to_ship, 511 ship_label = flavour.deliver_to_ship and Ship.MakeRandomLabel() or nil 512 } 513 514 ad.desc = string.interp(l["ADTEXT_" .. flavour.id], { cash = Format.Money(ad.reward, false) }) 515 516 local ref = station:AddAdvert({ 517 description = ad.desc, 518 icon = flavour.id == "RESCUE" and "searchrescue" or "haul", 519 onChat = onChat, 520 onDelete = onDelete, 521 isEnabled = isEnabled 522 }) 523 ads[ref] = ad 524 end 525end 526 527local onCreateBB = function (station) 528 local num = Engine.rand:Integer(0, math.ceil(Game.system.population * Game.system.lawlessness)) 529 for i = 1, num do 530 makeAdvert(station) 531 end 532end 533 534local onUpdateBB = function (station) 535 for ref, ad in pairs(ads) do 536 if ad.due < Game.time + 5*24*60*60 then -- five day timeout 537 ad.station:RemoveAdvert(ref) 538 end 539 end 540 if Engine.rand:Integer(4*24*60*60) < 60*60 then -- roughly once every four days 541 makeAdvert(station) 542 end 543end 544 545local onShipEquipmentChanged = function (ship, equipment) 546 if not ship:IsPlayer() or equipment == nil or equipment:GetDefaultSlot() ~= "cargo" then return end 547 548 for ref, mission in pairs(missions) do 549 if not mission.police and not Game.system:IsCommodityLegal(equipment.name) and not ship:IsDocked() and mission.location:IsSameSystem(Game.system.path) then 550 if (1 - Game.system.lawlessness) > Engine.rand:Number(4) then 551 local station = ship:FindNearestTo("SPACESTATION") 552 if station then mission.police = spawnPolice(station) end 553 end 554 end 555 end 556end 557 558-- The attacker could be a ship or the planet 559-- If scooped or destroyed by self-destruction, attacker is nil 560local onCargoDestroyed = function (body, attacker) 561 for ref, mission in pairs(missions) do 562 for i, e in pairs(mission.debris) do 563 if body == e.body then 564 e.body = nil 565 if body == mission.destination then 566 -- remove NavButton 567 mission.destination = nil 568 end 569 if attacker and (mission.return_to_station or mission.deliver_to_ship) then 570 mission.status = "FAILED" 571 end 572 if mission.destination == nil then 573 for i, e in pairs(mission.debris) do 574 if e.body ~= nil then 575 -- set next target 576 mission.destination = e.body 577 break 578 end 579 end 580 end 581 break 582 end 583 end 584 end 585end 586 587local onJettison = function (ship, cargo) 588 if not ship:IsPlayer() then return end 589 590 for ref, mission in pairs(missions) do 591 if mission.reward > 0 and not mission.warning then 592 for i, e in pairs(mission.debris) do 593 if cargo == e.cargo then 594 mission.warning = true 595 Comms.ImportantMessage(l.WARNING, mission.client.name) 596 break 597 end 598 end 599 end 600 end 601end 602 603local onShipHit = function (ship, attacker) 604 if ship:IsPlayer() then return end 605 if attacker == nil or not attacker:isa('Ship') then return end 606 607 for ref, mission in pairs(missions) do 608 if mission.police then 609 for _, s in pairs(mission.police) do 610 if s == ship then 611 ship:AIKill(attacker) 612 break 613 end 614 end 615 elseif mission.client_ship == ship then 616 ship:AIKill(attacker) 617 break 618 end 619 end 620end 621 622local onShipDestroyed = function (ship, attacker) 623 if ship:IsPlayer() then return end 624 625 for ref, mission in pairs(missions) do 626 if mission.police then 627 for i, s in pairs(mission.police) do 628 if s == ship then 629 table.remove(mission.police, i) 630 break 631 end 632 end 633 elseif mission.client_ship == ship then 634 mission.client_ship = nil 635 local msg = string.interp(l.SHIP_DESTROYED, { 636 shipid = mission.ship_label, 637 station = mission.station:GetSystemBody().name 638 }) 639 Comms.ImportantMessage(msg, mission.client.name) 640 break 641 end 642 end 643end 644 645local onShipDocked = function (player, station) 646 if not player:IsPlayer() then return end 647 648 for ref, mission in pairs(missions) do 649 if mission.police then 650 for _, s in pairs(mission.police) do 651 if station.type == "STARPORT_SURFACE" then 652 s:AIEnterLowOrbit(Space.GetBody(station:GetSystemBody().parent.index)) 653 else 654 s:AIFlyTo(station) 655 end 656 end 657 end 658 659 if mission.return_to_station or mission.deliver_to_ship and not mission.client_ship and mission.station == station.path then 660 661 -- unload mission cargo 662 for i, e in pairs(mission.debris) do 663 if e.body == nil then 664 if player:RemoveEquip(e.cargo, 1, "cargo") == 1 then 665 mission.debris[i] = nil 666 mission.amount = mission.amount - 1 667 end 668 end 669 end 670 if mission.amount == 0 then 671 mission.status = "COMPLETED" 672 elseif mission.destination == nil then 673 mission.status = "FAILED" 674 end 675 676 if mission.status == "COMPLETED" or mission.status == "FAILED" then 677 removeMission(mission, ref) 678 end 679 680 -- remove stale missions, if any 681 -- all cargo related to flavour 1 and 2 scooped or destroyed 682 elseif mission.reward < 0 and mission.destination == nil then 683 mission:Remove() 684 missions[ref] = nil 685 end 686 end 687end 688 689local onShipUndocked = function (player, station) 690 if not player:IsPlayer() then return end 691 692 for ref, mission in pairs(missions) do 693 if mission.police then 694 for _, s in pairs(mission.police) do 695 s:AIKill(player) 696 end 697 end 698 699 if mission.deliver_to_ship and not mission.in_progess then 700 mission.in_progress = true 701 transferCargo(mission, ref) 702 end 703 end 704end 705 706local getPopulatedPlanets = function (system) 707 local planets = {} 708 709 for _, p in ipairs(system:GetBodyPaths()) do 710 if p:GetSystemBody().population > 0 then 711 table.insert(planets, p) 712 end 713 end 714 return planets 715end 716 717local onEnterSystem = function (ship) 718 if not ship:IsPlayer() or Game.system.population == 0 then return end 719 720 local planets = getPopulatedPlanets(Game.system) 721 local num = Engine.rand:Integer(0, math.ceil(Game.system.population * Game.system.lawlessness)) 722 723 flavours[LEGAL].cargo_type, flavours[ILLEGAL].cargo_type = sortGoods(Equipment.cargo) 724 725 -- spawn random cargo (legal or illegal goods) 726 for i = 1, num do 727 local planet = planets[Engine.rand:Integer(1, #planets)] 728 local radius = planet:GetSystemBody().radius 729 local flavour = flavours[Engine.rand:Integer(LEGAL, ILLEGAL)] 730 local debris = flavour.cargo_type 731 if #debris > 0 then 732 spawnDebris(debris, flavour.amount, planet, radius * 1.2, radius * 3.5, mission_time) 733 end 734 end 735end 736 737local onLeaveSystem = function (ship) 738 if ship:IsPlayer() then 739 for ref, mission in pairs(missions) do 740 mission.destination = nil 741 mission.police = nil 742 if mission.client_ship then 743 mission.client_ship = nil 744 mission.status = "FAILED" 745 end 746 for i, e in pairs(mission.debris) do 747 e.body = nil 748 end 749 end 750 planets = nil 751 flavours[LEGAL].cargo_type = nil 752 flavours[ILLEGAL].cargo_type = nil 753 end 754end 755 756local onReputationChanged = function (oldRep, oldKills, newRep, newKills) 757 for ref, ad in pairs(ads) do 758 local oldQualified = isQualifiedFor(oldRep, ad) 759 if isQualifiedFor(newRep, ad) ~= oldQualified then 760 Event.Queue("onAdvertChanged", ad.station, ref); 761 end 762 end 763end 764 765local buildMissionDescription = function(mission) 766 local ui = require 'pigui' 767 local desc = {} 768 769 desc.description = mission.introtext:interp({ 770 client = mission.client.name, 771 shipid = mission.ship_label, 772 star = mission.star:GetSystemBody().name, 773 planet = mission.planet:GetSystemBody().name, 774 cash = Format.Money(math.abs(mission.reward), false) 775 }) 776 777 desc.details = { 778 { l.CLIENT, mission.client.name }, 779 { l.SPACEPORT, mission.station:GetSystemBody().name }, 780 mission.client_ship and { l.SHIP, mission.client_ship.label } or false, 781 false, 782 { l.DEADLINE, ui.Format.Date(mission.due) } 783 } 784 785 desc.client = mission.client 786 desc.location = mission.destination or nil 787 if mission.deliver_to_ship then 788 desc.returnLocation = mission.client_ship or mission.station 789 end 790 791 return desc 792end 793 794local loaded_data 795 796local onGameStart = function () 797 ads = {} 798 missions = {} 799 800 if loaded_data and loaded_data.ads then 801 802 for k, ad in pairs(loaded_data.ads) do 803 local ref = ad.station:AddAdvert({ 804 description = ad.desc, 805 icon = ad.id == "RESCUE" and "searchrescue" or "haul", 806 onChat = onChat, 807 onDelete = onDelete, 808 isEnabled = isEnabled 809 }) 810 ads[ref] = ad 811 end 812 813 missions = loaded_data.missions 814 815 loaded_data = nil 816 817 for ref, mission in pairs(missions) do 818 if mission.deliver_to_ship then 819 mission.in_progress = true 820 transferCargo(mission, ref) 821 end 822 end 823 end 824end 825 826local onGameEnd = function () 827 planets = nil 828 flavours[LEGAL].cargo_type = nil 829 flavours[ILLEGAL].cargo_type = nil 830end 831 832local serialize = function () 833 return { ads = ads, missions = missions } 834end 835 836local unserialize = function (data) 837 loaded_data = data 838end 839 840Event.Register("onCreateBB", onCreateBB) 841Event.Register("onUpdateBB", onUpdateBB) 842Event.Register("onShipEquipmentChanged", onShipEquipmentChanged) 843Event.Register("onShipDocked", onShipDocked) 844Event.Register("onShipUndocked", onShipUndocked) 845Event.Register("onShipHit", onShipHit) 846Event.Register("onShipDestroyed", onShipDestroyed) 847Event.Register("onJettison", onJettison) 848Event.Register("onCargoDestroyed", onCargoDestroyed) 849Event.Register("onEnterSystem", onEnterSystem) 850Event.Register("onLeaveSystem", onLeaveSystem) 851Event.Register("onGameStart", onGameStart) 852Event.Register("onGameEnd", onGameEnd) 853Event.Register("onReputationChanged", onReputationChanged) 854 855Mission.RegisterType("Scoop", l.SCOOP, buildMissionDescription) 856 857Serializer:Register("Scoop", serialize, unserialize) 858