1-- Name: Clash in Shangri-La (PVP)
2-- Description: Since its creation, the Shangri-La station was governed by a multi-ethnic consortium that assured the station's independence across the conflicts that shook the sector.
3---
4--- However, the station's tranquility came to an abrupt end when most of the governing consortium's members were assassinated under a Exuari false flag operation.
5---
6--- Now the station is in a state of civil war, with infighting breaking out between warring factions.
7---
8--- Both the neighboring Human Navy and Kraylor are worried that the breakdown of order in Shangri-La could tilt the balance of power in their opponent's favor, and sent "peacekeepers" to shift the situation to their own advantage.
9---
10--- The Human Navy's HNS Gallipoli and Kraylor's Crusader Naa'Tvek face off in an all-out battle for Shangri-La.
11-- Type: PvP
12
13--- Scenario
14-- @script scenario_81_pvp
15
16-- Global variables in this scenario
17
18-- Independent station
19local shangri_la
20
21-- Human Navy and Kraylor
22local gallipoli
23local crusader
24
25local shipyard_human
26local shipyard_kraylor
27
28local troops_human
29local troops_kraylor
30
31local respawn_human
32local respawn_kraylor
33
34local points_human
35local points_kraylor
36
37-- Timers
38local time
39local wave_timer
40local troop_timer
41
42--- Initialize scenario.
43function init()
44    troops_human = {}
45    troops_kraylor = {}
46
47    -- Stations
48    shangri_la = SpaceStation():setPosition(10000, 10000):setTemplate("Large Station"):setFaction("Independent"):setRotation(random(0, 360)):setCallSign("Shangri-La"):setCommsFunction(shangrilaComms)
49
50    do
51        local faction = "Human Navy"
52        shipyard_human = SpaceStation():setPosition(-7500, 15000):setTemplate("Small Station"):setFaction(faction):setRotation(random(0, 360)):setCallSign("Mobile Shipyard"):setCommsFunction(stationComms)
53        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(-8000, 16500):orderDefendTarget(shipyard_human):setScannedByFaction(faction, true)
54        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(-6000, 13500):orderDefendTarget(shipyard_human):setScannedByFaction(faction, true)
55        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(-7000, 14000):orderDefendTarget(shipyard_human):setScannedByFaction(faction, true)
56        CpuShip():setTemplate("Nirvana R5"):setFaction(faction):setPosition(-8000, 14000):orderDefendTarget(shipyard_human):setScannedByFaction(faction, true)
57    end
58
59    do
60        local faction = "Kraylor"
61        shipyard_kraylor = SpaceStation():setPosition(27500, 5000):setTemplate("Small Station"):setFaction(faction):setRotation(random(0, 360)):setCallSign("Forward Command"):setCommsFunction(stationComms)
62        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(29000, 5000):orderDefendTarget(shipyard_kraylor):setScannedByFaction(faction, true)
63        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(25000, 5000):orderDefendTarget(shipyard_kraylor):setScannedByFaction(faction, true)
64        CpuShip():setTemplate("Phobos T3"):setFaction(faction):setPosition(27500, 6000):orderDefendTarget(shipyard_kraylor):setScannedByFaction(faction, true)
65        CpuShip():setTemplate("Nirvana R5"):setFaction(faction):setPosition(27000, 5000):orderDefendTarget(shipyard_kraylor):setScannedByFaction(faction, true)
66    end
67
68    -- Spawn players
69    gallipoli = PlayerSpaceship():setFaction("Human Navy"):setTemplate("Atlantis"):setPosition(-8500, 15000):setCallSign("HNS Gallipoli"):setScannedByFaction("Kraylor", false)
70    crusader = PlayerSpaceship():setFaction("Kraylor"):setTemplate("Atlantis"):setPosition(26500, 5000):setCallSign("Crusader Naa'Tvek"):setScannedByFaction("Human Navy", false)
71
72    -- Initialize timers
73    time = 0
74    wave_timer = 0
75    troop_timer = 0
76    respawn_human = 0
77    respawn_kraylor = 0
78
79    points_human = 0
80    points_kraylor = 0
81
82    -- Create terrain
83    create(Asteroid, 20, 5000, 10000, 10000, 10000)
84    create(VisualAsteroid, 10, 5000, 10000, 10000, 10000)
85    create(Mine, 10, 5000, 10000, 10000, 10000)
86
87    -- Brief the players
88    shipyard_human:sendCommsMessage(
89        gallipoli,
90        [[Captain, it seems that the Kraylor are moving to take the Shangri-La station in sector F5!
91
92Provide cover while our troop transports board the station to reclaim it.
93
94Good luck, and stay safe.]]
95    )
96
97    shipyard_kraylor:sendCommsMessage(
98        crusader,
99        [[Greetings, Crusader.
100
101Your mission is to secure the Shangri-La station in sector F5. The feeble humans think it's theirs for the taking.
102
103Support our glorious soldiers by preventing the heretics from harming our transports, and cleanse all enemy opposition!]]
104    )
105
106    -- Spawn the first wave
107    do
108        local faction = "Human Navy"
109        local transport = spawnTransport():setFaction(faction):setPosition(-7000, 15000):orderDock(shangri_la):setScannedByFaction(faction, true)
110        table.insert(troops_human, transport)
111        CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(-7000, 15500):orderDefendTarget(transport):setScannedByFaction(faction, true)
112        CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(-7000, 14500):orderDefendTarget(transport):setScannedByFaction(faction, true)
113    end
114
115    do
116        local faction = "Kraylor"
117        local transport = spawnTransport():setFaction(faction):setPosition(26500, 5000):orderDock(shangri_la):setScannedByFaction(faction, true)
118        table.insert(troops_kraylor, transport)
119        CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(26500, 5500):orderDefendTarget(transport):setScannedByFaction(faction, true)
120        CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(26500, 4500):orderDefendTarget(transport):setScannedByFaction(faction, true)
121    end
122end
123
124--- Compile and return status report.
125--
126-- @treturn string the status report
127function getStatusReport()
128    return table.concat(
129        {
130            "Here's the latest news from the front.",
131            string.format("Human dominance: %d", points_human),
132            string.format("Kraylor dominance: %d", points_kraylor),
133            string.format("Time elapsed: %.0f", time)
134        },
135        "\n"
136    )
137end
138
139--- Comms with independent station _Shangri-La_.
140--
141-- If players call Shangri-La, provide a status report
142function shangrilaComms()
143    setCommsMessage([[Your faction's militia commander picks up:
144
145What can we do for you, Captain?]])
146    addCommsReply(
147        "Give us a status report.",
148        function()
149            setCommsMessage(getStatusReport())
150        end
151    )
152end
153
154--- Comms for station(s).
155--
156-- If friendly players call a station, provide a status report and offer
157-- reinforcements at a reputation cost.
158function stationComms()
159    if comms_source:isFriendly(comms_target) then
160        if not comms_source:isDocked(comms_target) then
161            setCommsMessage([[A dispatcher responds:
162
163Greetings, Captain. If you want supplies, please dock with us.]])
164        else
165            setCommsMessage([[A dispatcher responds:
166
167Greetings, Captain. What can we do for you?]])
168        end
169
170        addCommsReply(
171            "I need a status report.",
172            function()
173                setCommsMessage(getStatusReport())
174            end
175        )
176
177        addCommsReply(
178            "Send in more troops. (100 reputation)",
179            function()
180                if not comms_source:takeReputationPoints(100) then
181                    setCommsMessage("Not enough reputation.")
182                    return
183                end
184                setCommsMessage("Aye, Captain. We've deployed a squad with fighter escort to support the assault on Shangri-La.")
185                if comms_target:getFaction() == "Human Navy" then
186                    local transport = spawnTransport():setFaction("Human Navy"):setPosition(comms_target:getPosition()):orderDock(shangri_la):setScannedByFaction("Human Navy", true)
187                    table.insert(troops_human, transport)
188                    CpuShip():setTemplate("MT52 Hornet"):setFaction(comms_target:getFaction()):setPosition(comms_target:getPosition()):orderDefendTarget(transport):setScannedByFaction(comms_source:getFaction(), true)
189                elseif comms_target:getFaction() == "Kraylor" then
190                    local transport = spawnTransport():setFaction("Kraylor"):setPosition(comms_target:getPosition()):orderDock(shangri_la):setScannedByFaction("Kraylor", true)
191                    table.insert(troops_kraylor, transport)
192                    CpuShip():setTemplate("MT52 Hornet"):setFaction(comms_target:getFaction()):setPosition(comms_target:getPosition()):orderDefendTarget(transport):setScannedByFaction(comms_source:getFaction(), true)
193                else
194                    -- Can usually not happen. Do nothing.
195                end
196            end
197        )
198
199        addCommsReply(
200            "We need some space-based firepower. (150 reputation)",
201            function()
202                if not comms_source:takeReputationPoints(150) then
203                    setCommsMessage("Not enough reputation.")
204                    return
205                end
206                setCommsMessage("Confirmed. We've dispatched a strike wing to support space superiority around Shangri-La.")
207                local strike_leader = CpuShip():setTemplate("Phobos T3"):setFaction(comms_target:getFaction()):setPosition(comms_target:getPosition()):orderDefendTarget(shangri_la):setScannedByFaction(comms_source:getFaction(), true)
208                CpuShip():setTemplate("MU52 Hornet"):setFaction(comms_target:getFaction()):setPosition(comms_target:getPosition()):orderFlyFormation(strike_leader, -1000, 0):setScannedByFaction(comms_source:getFaction(), true)
209                CpuShip():setTemplate("MU52 Hornet"):setFaction(comms_target:getFaction()):setPosition(comms_target:getPosition()):orderFlyFormation(strike_leader, 1000, 0):setScannedByFaction(comms_source:getFaction(), true)
210            end
211        )
212
213        if comms_source:isDocked(comms_target) then
214            addCommsReply("We need supplies.", supplyDialogue)
215        end
216    else
217        setCommsMessage("We'll bring your destruction!")
218    end
219end
220
221--- Add reply.
222function addCommsReplySupply(args)
223    local price = args.price
224    local missile_type = args.missile_type
225    addCommsReply(
226        string.format(args.request .. " (%d rep each)", price),
227        function()
228            if not comms_source:isDocked(comms_target) then
229                setCommsMessage("You need to stay docked for that action.")
230                return
231            end
232            if not comms_source:takeReputationPoints(price * (comms_source:getWeaponStorageMax(missile_type) - comms_source:getWeaponStorage(missile_type))) then
233                setCommsMessage("Not enough reputation.")
234                return
235            end
236            if comms_source:getWeaponStorage(missile_type) >= comms_source:getWeaponStorageMax(missile_type) then
237                setCommsMessage(args.reply_full)
238                addCommsReply("Back", supplyDialogue)
239            else
240                comms_source:setWeaponStorage(missile_type, comms_source:getWeaponStorageMax(missile_type))
241                setCommsMessage(args.reply_filled)
242                addCommsReply("Back", supplyDialogue)
243            end
244        end
245    )
246end
247
248--- Comms supplyDialogue.
249function supplyDialogue()
250    setCommsMessage("What supplies do you need?")
251
252    addCommsReplySupply {
253        missile_type = "Homing",
254        price = 2,
255        request = "Do you have spare homing missiles for us?",
256        reply_full = "Sorry, Captain, but you are fully stocked with homing missiles.",
257        reply_filled = "We've replenished your homing missile supply."
258    }
259
260    addCommsReplySupply {
261        missile_type = "Mine",
262        price = 2,
263        request = "Please re-stock our mines.",
264        reply_full = "Captain, you already have all the mines you can fit in that ship.",
265        reply_filled = "These mines are yours."
266    }
267
268    addCommsReplySupply {
269        missile_type = "Nuke",
270        price = 15,
271        request = "Can you supply us with some nukes?",
272        reply_full = "Your nukes are already charged and primed for destruction.",
273        reply_filled = "You are fully loaded and ready to explode things."
274    }
275
276    addCommsReplySupply {
277        missile_type = "EMP",
278        price = 10,
279        request = "Please re-stock our EMP missiles.",
280        reply_full = "All storage for EMP missiles is already full, Captain.",
281        reply_filled = "We've recalibrated the electronics and fitted you with all the EMP missiles you can carry."
282    }
283
284    addCommsReplySupply {
285        missile_type = "HVLI",
286        price = 2,
287        request = "Can you restock us with HVLI?",
288        reply_full = "Sorry, Captain, but you are fully stocked with HVLIs.",
289        reply_filled = "We've replenished your HVLI supply."
290    }
291
292    addCommsReply("Back to main menu", stationComms)
293end
294
295--- Update.
296function update(delta)
297    -- Increment timers
298    time = time + delta
299    wave_timer = wave_timer + delta
300    troop_timer = troop_timer + delta
301
302    -- If the Gallipoli is destroyed ...
303    if (not gallipoli:isValid()) then
304        if respawn_human > 20 then
305            -- ... and 20 seconds have passed, spawn the Heinlein.
306            gallipoli = PlayerSpaceship():setFaction("Human Navy"):setTemplate("Atlantis"):setPosition(-8500, 15000):setCallSign("HNS Heinlein"):setScannedByFaction("Kraylor", false)
307        else
308            -- Otherwise, increment the respawn timer.
309            respawn_human = respawn_human + delta
310        end
311    end
312
313    -- Ditto for the Crusader.
314    if (not crusader:isValid()) then
315        if respawn_kraylor > 20 then
316            crusader = PlayerSpaceship():setFaction("Kraylor"):setTemplate("Atlantis"):setPosition(19000, -14500):setCallSign("Crusader Elak'raan"):setScannedByFaction("Human Navy", false)
317        else
318            respawn_kraylor = respawn_kraylor + delta
319        end
320    end
321
322    -- Increment reputation for both sides.
323    gallipoli:addReputationPoints(delta * 0.3)
324    crusader:addReputationPoints(delta * 0.3)
325
326    -- If a faction has no station or flagship, it loses.
327    -- If a faction scores 50 points, it wins.
328    if ((not gallipoli:isValid()) and (not shipyard_human:isValid())) or points_kraylor > 50 then
329        victory("Kraylor")
330    end
331
332    if ((not crusader:isValid()) and (not shipyard_kraylor:isValid())) or points_human > 50 then
333        victory("Human Navy")
334    end
335
336    -- If either flagship is destroyed, its opponent gains a reputation bonus, and
337    -- its opponent's faction gains victory points.
338    if (not gallipoli:isValid()) then
339        shipyard_kraylor:sendCommsMessage(
340            crusader,
341            [[Well done, Crusader!
342
343The pathetic Human flagship has been disabled. Go for the victory!]]
344        )
345        crusader:addReputationPoints(50)
346        points_kraylor = points_kraylor + 5
347        respawn_human = 0
348    end
349
350    if (not crusader:isValid()) then
351        shipyard_human:sendCommsMessage(
352            gallipoli,
353            [[Good job, Captain!
354
355With the Kraylor flagship out of the way, we can land the final blow!]]
356        )
357        gallipoli:addReputationPoints(50)
358        points_human = points_human + 5
359        respawn_kraylor = 0
360    end
361
362    -- Every 150 seconds, spawn a troop transport and 2 fighters as escorts for
363    -- each faction.
364    if wave_timer > 150 and (shipyard_human:isValid()) then
365        do
366            local faction = "Human Navy"
367            local line = random(0, 20) * 500
368            local transport = spawnTransport():setFaction(faction):setPosition(-7000, 5000 + line):orderDock(shangri_la):setScannedByFaction(faction, true)
369            table.insert(troops_human, transport)
370            CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(-7000, 5500 + line):orderDefendTarget(transport):setScannedByFaction(faction, true)
371            CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(-7000, 4500 + line):orderDefendTarget(transport):setScannedByFaction(faction, true)
372        end
373
374        do
375            local faction = "Kraylor"
376            local line = random(0, 20) * 500
377            local transport = spawnTransport():setFaction(faction):setPosition(27000, -5000 + line):orderDock(shangri_la):setScannedByFaction(faction, true)
378            table.insert(troops_kraylor, transport)
379            CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(27000, -5500 + line):orderDefendTarget(transport):setScannedByFaction(faction, true)
380            CpuShip():setTemplate("MT52 Hornet"):setFaction(faction):setPosition(27000, -4500 + line):orderDefendTarget(transport):setScannedByFaction(faction, true)
381        end
382
383        wave_timer = 0
384    end
385
386    -- Count transports. Every 10 seconds, award 1 point per transport docked
387    -- with Shangri-La.
388    if troop_timer > 10 then
389        for _, transport in ipairs(troops_kraylor) do
390            if transport:isValid() and transport:isDocked(shangri_la) then
391                points_kraylor = points_kraylor + 1
392            end
393        end
394
395        for _, transport in ipairs(troops_human) do
396            if transport:isValid() and transport:isDocked(shangri_la) then
397                points_human = points_human + 1
398            end
399        end
400
401        troop_timer = 0
402    end
403
404    -- If Shangri-La is destroyed, nobody wins.
405    if (not shangri_la:isValid()) then
406        victory("Independents")
407    end
408end
409
410--- Spawn a troop transport.
411--
412-- @treturn CpuShip
413function spawnTransport()
414    local ship = CpuShip():setTemplate("Personnel Freighter 2")
415    ship:setHullMax(100):setHull(100)
416    ship:setShieldsMax(50, 50):setShields(50, 50)
417    ship:setImpulseMaxSpeed(100):setRotationMaxSpeed(10)
418    return ship
419end
420
421--[[ Distribute a `number` of `object_type` objects at a random distance
422     between `dist_min` and `dist_max` around the coordinates `cx, cy`. ]] --
423function create(object_type, number, dist_min, dist_max, cx, cy)
424    for _ = 1, number do
425        local phi = random(0, 2 * math.pi)
426        local distance = random(dist_min, dist_max)
427        local x = cx + math.cos(phi) * distance
428        local y = cy + math.sin(phi) * distance
429        object_type():setPosition(x, y)
430    end
431end
432