First commit
This commit is contained in:
parent
e6d1ac54c9
commit
59aa6514dc
5
.luarc.json
Normal file
5
.luarc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"diagnostics.globals": [
|
||||
"data"
|
||||
]
|
||||
}
|
||||
BIN
Graphics/Base/base_core.png
Normal file
BIN
Graphics/Base/base_core.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
Graphics/Tech/nauvis-tech.png
Normal file
BIN
Graphics/Tech/nauvis-tech.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
165
base.lua
Normal file
165
base.lua
Normal file
@ -0,0 +1,165 @@
|
||||
local drop_zone = require("drop_zone")
|
||||
local base = {}
|
||||
|
||||
local COLORS = {
|
||||
{r = .90, g = .45, b = .10, a = 1}, {r = .95, g = .20, b = .20, a = 1},
|
||||
{r = .70, g = .20, b = .90, a = 1}, {r = .20, g = .85, b = .20, a = 1},
|
||||
{r = .20, g = .60, b = .95, a = 1},
|
||||
}
|
||||
|
||||
local function notify(force_name, message)
|
||||
local force = game.forces[force_name]
|
||||
if force then for _, player in pairs(force.players) do if player.valid then player.print(message) end end end
|
||||
end
|
||||
|
||||
local function count(force_name)
|
||||
local total = 0
|
||||
for _ in pairs(storage.force_bases[force_name] or {}) do total = total + 1 end
|
||||
return total
|
||||
end
|
||||
|
||||
local function color_for(force_name)
|
||||
return storage.force_colors[force_name] or {r = 1, g = 1, b = 1, a = 1}
|
||||
end
|
||||
|
||||
function base.assign_force_color(force_name)
|
||||
if storage.force_colors[force_name] then return end
|
||||
local used = 0
|
||||
for _ in pairs(storage.force_colors) do used = used + 1 end
|
||||
storage.force_colors[force_name] = COLORS[(used % #COLORS) + 1]
|
||||
end
|
||||
|
||||
function base.register(entity)
|
||||
if not (entity and entity.valid) then return end
|
||||
local force_name = entity.force.name
|
||||
storage.force_bases[force_name] = storage.force_bases[force_name] or {}
|
||||
storage.force_bases[force_name][entity.unit_number] = true
|
||||
local color = color_for(force_name)
|
||||
entity.color = color
|
||||
storage.base_label_ids[entity.unit_number] = rendering.draw_text{
|
||||
text = force_name, surface = entity.surface, target = entity, target_offset = {0, -3.5},
|
||||
color = color, scale = 2, font = "default-bold", alignment = "center", visible = true,
|
||||
}
|
||||
notify(force_name, "[color=green][BASE] A base has been established at (" .. math.floor(entity.position.x)
|
||||
.. ", " .. math.floor(entity.position.y) .. ") on " .. entity.surface.name .. ".[/color]")
|
||||
end
|
||||
|
||||
function base.spawn_initial(force_name, planet_name)
|
||||
local planet = game.planets[planet_name]
|
||||
if not planet then return end
|
||||
local surface = planet.create_surface()
|
||||
local position = surface.find_non_colliding_position("market", {8, 0}, 200, 1) or {x = 8, y = 0}
|
||||
local entity = surface.create_entity{name = "pvp-base", position = position, force = game.forces[force_name], raise_built = true}
|
||||
if entity and entity.valid then base.register(entity) end
|
||||
end
|
||||
|
||||
local function destroyed(entity)
|
||||
local force_name = entity.force.name
|
||||
if storage.force_bases[force_name] then storage.force_bases[force_name][entity.unit_number] = nil end
|
||||
local label = storage.base_label_ids[entity.unit_number]
|
||||
if label and label.valid then label.destroy() end
|
||||
storage.base_label_ids[entity.unit_number] = nil
|
||||
local remaining = count(force_name)
|
||||
if remaining > 0 then
|
||||
notify(force_name, "[color=orange][BASE] A base was destroyed! You have " .. remaining .. " base(s) remaining. Respawn is still active.[/color]")
|
||||
else
|
||||
notify(force_name, "[color=red][BASE] ALL BASES DESTROYED! Any player who dies now is PERMANENTLY ELIMINATED. Rebuild a base core to restore respawning.[/color]")
|
||||
end
|
||||
end
|
||||
|
||||
local function spectator(player)
|
||||
player.set_controller{type = defines.controllers.spectator}
|
||||
player.print("[color=red]You have been permanently eliminated.[/color]")
|
||||
end
|
||||
|
||||
function base.on_built_entity(event)
|
||||
if event.entity and event.entity.valid and event.entity.name == "pvp-base" then base.register(event.entity) end
|
||||
end
|
||||
|
||||
function base.on_entity_died(event)
|
||||
if event.entity and event.entity.valid and event.entity.name == "pvp-base" then destroyed(event.entity) end
|
||||
end
|
||||
|
||||
function base.on_player_died(event)
|
||||
local player = game.players[event.player_index]
|
||||
if not (player and player.valid) then return end
|
||||
if not (storage.player_team_joined and storage.player_team_joined[player.index]) then return end
|
||||
local force_name = player.force.name
|
||||
if storage.permadead[force_name] and storage.permadead[force_name][player.index] then return end
|
||||
if count(force_name) > 0 then
|
||||
player.print("[color=yellow]You died. Respawning... (Base is alive)[/color]")
|
||||
else
|
||||
storage.permadead[force_name] = storage.permadead[force_name] or {}
|
||||
storage.permadead[force_name][player.index] = true
|
||||
storage.pending_spectator[player.index] = force_name
|
||||
player.print("[color=red]You died while your team had no base. You are PERMANENTLY ELIMINATED.[/color]")
|
||||
end
|
||||
end
|
||||
|
||||
function base.on_player_respawned(event)
|
||||
local player = game.players[event.player_index]
|
||||
if not (player and player.valid) then return end
|
||||
if not (storage.player_team_joined and storage.player_team_joined[player.index]) then return end
|
||||
local force_name = player.force.name
|
||||
if storage.permadead[force_name] and storage.permadead[force_name][player.index] then spectator(player); return end
|
||||
if count(force_name) == 0 then
|
||||
storage.permadead[force_name] = storage.permadead[force_name] or {}
|
||||
storage.permadead[force_name][player.index] = true
|
||||
spectator(player)
|
||||
return
|
||||
end
|
||||
for planet_name, assigned_force in pairs(storage.planet_teams or {}) do
|
||||
if assigned_force == force_name then
|
||||
local planet = game.planets[planet_name]
|
||||
if planet and planet.surface then drop_zone.teleport_to_planet(player, planet.surface) end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function base.on_pre_player_mined_item(event)
|
||||
local entity, player = event.entity, game.players[event.player_index]
|
||||
if entity and entity.valid and entity.name == "pvp-base" and player and player.valid then
|
||||
player.mining_state = {mining = false}
|
||||
player.print("[color=red]The base cannot be moved once placed. Destroy it to retrieve the core.[/color]")
|
||||
end
|
||||
end
|
||||
|
||||
function base.on_robot_pre_mined(event)
|
||||
local entity = event.entity
|
||||
if entity and entity.valid and entity.name == "pvp-base" then entity.cancel_deconstruction("all") end
|
||||
end
|
||||
|
||||
function base.on_tick(event)
|
||||
if not storage.crash_site_cleanup_done then
|
||||
local nauvis = game.planets["nauvis"] and game.planets["nauvis"].surface
|
||||
if nauvis then
|
||||
local crash_names = {
|
||||
"crash-site-spaceship", "crash-site-spaceship-wreck-big-1", "crash-site-spaceship-wreck-big-2",
|
||||
"crash-site-spaceship-wreck-medium-1", "crash-site-spaceship-wreck-medium-2", "crash-site-spaceship-wreck-medium-3",
|
||||
"crash-site-spaceship-wreck-small-1", "crash-site-spaceship-wreck-small-2", "crash-site-spaceship-wreck-small-3",
|
||||
"crash-site-spaceship-wreck-small-4", "crash-site-spaceship-wreck-small-5", "crash-site-spaceship-wreck-small-6",
|
||||
"crash-site-chest-1", "crash-site-chest-2",
|
||||
}
|
||||
for _, name in pairs(crash_names) do
|
||||
for _, entity in pairs(nauvis.find_entities_filtered{name = name}) do if entity.valid then entity.destroy() end end
|
||||
end
|
||||
for _, type in pairs({"fire", "smoke", "explosion", "particle"}) do
|
||||
for _, entity in pairs(nauvis.find_entities_filtered{type = type, area = {{-200, -200}, {200, 200}}}) do if entity.valid then entity.destroy() end end
|
||||
end
|
||||
if event.tick >= 300 then storage.crash_site_cleanup_done = true end
|
||||
end
|
||||
end
|
||||
if event.tick % 10 ~= 0 or not next(storage.pending_spectator) then return end
|
||||
for index in pairs(storage.pending_spectator) do
|
||||
local player = game.players[index]
|
||||
if player and player.valid and not player.character then spectator(player); storage.pending_spectator[index] = nil end
|
||||
end
|
||||
end
|
||||
|
||||
function base.status_command(event)
|
||||
local player = game.players[event.player_index]
|
||||
if player then for force_name in pairs(storage.force_bases) do player.print(force_name .. ": " .. count(force_name) .. " base(s)") end end
|
||||
end
|
||||
|
||||
return base
|
||||
69
control.lua
Normal file
69
control.lua
Normal file
@ -0,0 +1,69 @@
|
||||
local observer = require("observer")
|
||||
local radar = require("radar")
|
||||
local drop_zone = require("drop_zone")
|
||||
local base = require("base")
|
||||
local setup = require("setup")
|
||||
local player_system = require("player")
|
||||
|
||||
-- One registration per Factorio event lets the separate systems share events safely.
|
||||
script.on_init(function() setup.on_init(observer); player_system.on_init() end)
|
||||
script.on_configuration_changed(setup.on_configuration_changed)
|
||||
|
||||
script.on_event(defines.events.on_player_created, function(event)
|
||||
player_system.on_player_created(event)
|
||||
setup.on_player_created(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_player_joined_game, function(event)
|
||||
player_system.on_player_joined_game(event)
|
||||
setup.on_player_joined_game(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_player_respawned, function(event)
|
||||
player_system.on_player_respawned(event)
|
||||
base.on_player_respawned(event)
|
||||
setup.on_player_respawned(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_player_changed_surface, function(event)
|
||||
player_system.on_player_changed_surface(event)
|
||||
drop_zone.on_player_changed_surface(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_chunk_generated, setup.on_chunk_generated)
|
||||
script.on_event(defines.events.on_built_entity, function(event)
|
||||
radar.on_built_entity(event)
|
||||
base.on_built_entity(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_entity_died, base.on_entity_died)
|
||||
script.on_event(defines.events.on_player_died, base.on_player_died)
|
||||
script.on_event(defines.events.on_pre_player_mined_item, base.on_pre_player_mined_item)
|
||||
script.on_event(defines.events.on_robot_pre_mined, base.on_robot_pre_mined)
|
||||
script.on_nth_tick(1800, radar.on_orbital_scan_tick)
|
||||
script.on_event(defines.events.on_cargo_pod_finished_ascending, drop_zone.on_cargo_pod_finished_ascending)
|
||||
script.on_event(defines.events.on_entity_damaged, drop_zone.on_entity_damaged)
|
||||
script.on_event(defines.events.on_lua_shortcut, drop_zone.on_lua_shortcut)
|
||||
script.on_event(defines.events.on_player_selected_area, drop_zone.on_player_selected_area)
|
||||
script.on_event(defines.events.on_gui_click, function(event)
|
||||
if not observer.on_gui_click(event) then setup.on_gui_click(event) end
|
||||
end)
|
||||
script.on_event("planet-pvp-toggle-observer", function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
if player then observer.toggle_window(player) end
|
||||
end)
|
||||
script.on_nth_tick(5, observer.on_tick)
|
||||
script.on_nth_tick(120, player_system.on_light_tick)
|
||||
script.on_event(defines.events.on_tick, base.on_tick)
|
||||
|
||||
commands.add_command("basestatus", "Show base count for every force", base.status_command)
|
||||
commands.add_command("ping", nil, function(command)
|
||||
local player = game.players[command.player_index]
|
||||
if not (player and player.valid and player.character) then return end
|
||||
for _, mode in pairs({"game", "chart"}) do
|
||||
rendering.draw_circle{color = {r = 1, g = 1, b = 0, a = 1}, radius = 3, width = 10, filled = false, target = player.position, surface = player.surface, players = player.force.players, time_to_live = 300, render_mode = mode}
|
||||
rendering.draw_text{text = "PING", surface = player.surface, target = {position = player.position, offset = {0, -2.5}}, color = {r = 1, g = 1, b = 0, a = 1}, players = player.force.players, alignment = "center", scale = 3, time_to_live = 300, render_mode = mode}
|
||||
end
|
||||
for _, target in pairs(player.force.players) do if target.valid then target.print(player.name .. " pinged their location") end end
|
||||
end)
|
||||
commands.add_command("observer", "Become an observer", function(command)
|
||||
local player = game.get_player(command.player_index)
|
||||
if not player then return end
|
||||
if not player.admin then player.print("Only admins can become observers."); return end
|
||||
observer.enable(player)
|
||||
end)
|
||||
164
data.lua
Normal file
164
data.lua
Normal file
@ -0,0 +1,164 @@
|
||||
data:extend({
|
||||
{
|
||||
type = "selection-tool",
|
||||
name = "drop-zone-selector",
|
||||
icon = "__base__/graphics/icons/artillery-targeting-remote.png",
|
||||
icon_size = 64,
|
||||
flags = {"not-stackable", "only-in-cursor"},
|
||||
stack_size = 1,
|
||||
select = {
|
||||
border_color = {r = 0, g = 1, b = 0},
|
||||
cursor_box_type = "copy",
|
||||
mode = {"nothing"}
|
||||
},
|
||||
alt_select = {
|
||||
border_color = {r = 1, g = 0.5, b = 0},
|
||||
cursor_box_type = "not-allowed",
|
||||
mode = {"nothing"}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "shortcut",
|
||||
name = "lock_drop_zone_shortcut",
|
||||
action = "lua",
|
||||
order = "z[spidertron-remote-b]",
|
||||
localised_name = "Drop Zone Selector",
|
||||
icon = "__base__/graphics/icons/artillery-targeting-remote.png",
|
||||
small_icon = "__base__/graphics/icons/artillery-targeting-remote.png"
|
||||
}
|
||||
})
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "simple-entity-with-owner",
|
||||
name = "pvp-base",
|
||||
icon = "__base__/graphics/icons/market.png",
|
||||
icon_size = 64,
|
||||
flags = { "placeable-neutral", "player-creation", "not-rotatable" },
|
||||
weight = 1000001,
|
||||
max_health = 2000,
|
||||
resistances = {
|
||||
{ type = "fire", decrease = 15, percent = 30 },
|
||||
{ type = "physical", decrease = 10, percent = 20 },
|
||||
{ type = "explosion", decrease = 20, percent = 40 },
|
||||
{ type = "laser", decrease = 5, percent = 20 },
|
||||
},
|
||||
loot = {
|
||||
{ type = "item", name = "pvp-base-core", amount_min = 1, amount_max = 1, independent_probability = 1 }
|
||||
},
|
||||
collision_box = { {-1.2, -1.2}, {1.2, 1.2} },
|
||||
selection_box = { {-1.5, -1.5}, {1.5, 1.5} },
|
||||
collision_mask = { layers = { object = true, player = true } },
|
||||
picture = {
|
||||
filename = "__base__/graphics/entity/market/market.png",
|
||||
priority = "extra-high",
|
||||
width = 156,
|
||||
height = 127,
|
||||
shift = { 1, 0.0001 },
|
||||
scale = 1.0,
|
||||
},
|
||||
minable = nil,
|
||||
map_color = { r = 0.9, g = 0.2, b = 0.2 },
|
||||
friendly_map_color = { r = 0.2, g = 0.8, b = 0.2 },
|
||||
alert_when_damaged = true,
|
||||
is_military_target = true,
|
||||
}
|
||||
})
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "item",
|
||||
name = "pvp-base-building",
|
||||
icon = "__base__/graphics/icons/market.png",
|
||||
icon_size = 64,
|
||||
subgroup = "other",
|
||||
order = "z[pvp-base-building]",
|
||||
stack_size = 1,
|
||||
weight = 1000001,
|
||||
place_result = "pvp-base",
|
||||
}
|
||||
})
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "item",
|
||||
name = "pvp-base-core",
|
||||
icon = "__Planet-PvP__/Graphics/Base/base_core.png",
|
||||
icon_size = 128,
|
||||
subgroup = "other",
|
||||
order = "z[pvp-base-core]",
|
||||
stack_size = 1,
|
||||
weight = 1000001,
|
||||
}
|
||||
})
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "recipe",
|
||||
name = "pvp-base-repair",
|
||||
enabled = true,
|
||||
categories = {"crafting"},
|
||||
energy_required = 60,
|
||||
ingredients = {
|
||||
{ type = "item", name = "pvp-base-core", amount = 1 },
|
||||
{ type = "item", name = "repair-pack", amount = 1 },
|
||||
},
|
||||
results = {
|
||||
{ type = "item", name = "pvp-base-building", amount = 1 },
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
if data.raw["character"] and data.raw["character"]["character"] then
|
||||
data.raw["character"]["character"].light = {
|
||||
{
|
||||
type = "basic",
|
||||
intensity = 0,
|
||||
size = 0,
|
||||
color = {r=0, g=0, b=0}
|
||||
}
|
||||
}
|
||||
data.raw["character"]["character"].flashlight = nil
|
||||
end
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "custom-input",
|
||||
name = "planet-pvp-toggle-observer",
|
||||
key_sequence = "O",
|
||||
consuming = "game-only",
|
||||
enabled_while_spectating = true
|
||||
}
|
||||
})
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "technology",
|
||||
name = "planet-discovery-nauvis",
|
||||
icon = "__Planet-PvP__/Graphics/Tech/nauvis-tech.png",
|
||||
icon_size = 210,
|
||||
essential = true,
|
||||
effects = {
|
||||
{
|
||||
type = "unlock-space-location",
|
||||
space_location = "nauvis",
|
||||
use_icon_overlay_constant = true
|
||||
},
|
||||
{
|
||||
type = "unlock-travel-to-space-platforms",
|
||||
modifier = true
|
||||
}
|
||||
},
|
||||
prerequisites = {"space-platform-thruster"},
|
||||
unit = {
|
||||
count = 1000,
|
||||
ingredients = {
|
||||
{"automation-science-pack", 1},
|
||||
{"logistic-science-pack", 1},
|
||||
{"chemical-science-pack", 1},
|
||||
{"space-science-pack", 1}
|
||||
},
|
||||
time = 60
|
||||
}
|
||||
}
|
||||
})
|
||||
81
drop_zone.lua
Normal file
81
drop_zone.lua
Normal file
@ -0,0 +1,81 @@
|
||||
local drop_zone = {}
|
||||
|
||||
function drop_zone.teleport_to_planet(player, surface)
|
||||
local planet_name = surface.name
|
||||
storage.player_targets = storage.player_targets or {}
|
||||
local target = storage.player_targets[player.force.name .. "_" .. planet_name]
|
||||
local position = target and target.position
|
||||
or surface.find_non_colliding_position("character", {0, 0}, 500, 2) or {x = 0, y = 0}
|
||||
local attempts = 0
|
||||
script.on_nth_tick(1, function()
|
||||
if player and player.valid and attempts < 30 then
|
||||
player.teleport(position, surface)
|
||||
if player.character then player.set_controller{type = defines.controllers.character, character = player.character} end
|
||||
attempts = attempts + 1
|
||||
else
|
||||
script.on_nth_tick(1, nil)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function drop_zone.on_player_changed_surface(event)
|
||||
local player = game.players[event.player_index]
|
||||
if not (player and player.valid) then return end
|
||||
local surface = player.surface
|
||||
if event.surface_index == surface.index or surface.platform or player.render_mode == defines.render_mode.chart then return end
|
||||
storage.player_surfaces = storage.player_surfaces or {}
|
||||
if storage.player_surfaces[player.index] == surface.index then return end
|
||||
storage.player_surfaces[player.index] = surface.index
|
||||
if player.controller_type == defines.controllers.character or player.controller_type == defines.controllers.cutscene then
|
||||
drop_zone.teleport_to_planet(player, surface)
|
||||
end
|
||||
end
|
||||
|
||||
function drop_zone.on_cargo_pod_finished_ascending(event)
|
||||
local pod = event.cargo_pod
|
||||
if not (pod and pod.valid) or not pod.cargo_pod_destination then return end
|
||||
local surface = pod.cargo_pod_destination.surface
|
||||
if not surface or surface.platform then return end
|
||||
local target = storage.player_targets and storage.player_targets[pod.force.name .. "_" .. surface.name]
|
||||
if target then pod.cargo_pod_destination = {type = defines.cargo_destination.surface, surface = surface, position = target.position} end
|
||||
end
|
||||
|
||||
function drop_zone.on_entity_damaged(event)
|
||||
local entity, cause = event.entity, event.cause
|
||||
if entity and entity.valid and entity.type == "character" and cause and cause.valid and cause.name == "cargo-pod" and cause.force == entity.force then
|
||||
entity.health = entity.health + event.final_damage_amount
|
||||
end
|
||||
end
|
||||
|
||||
function drop_zone.on_lua_shortcut(event)
|
||||
if event.prototype_name == "lock_drop_zone_shortcut" then
|
||||
local player = game.players[event.player_index]
|
||||
if player and player.cursor_stack then player.cursor_stack.set_stack{name = "drop-zone-selector", count = 1} end
|
||||
end
|
||||
end
|
||||
|
||||
function drop_zone.on_player_selected_area(event)
|
||||
if event.item ~= "drop-zone-selector" then return end
|
||||
local player, surface = game.players[event.player_index], event.surface
|
||||
storage.player_targets = storage.player_targets or {}
|
||||
local pos = {x = (event.area.left_top.x + event.area.right_bottom.x) / 2, y = (event.area.left_top.y + event.area.right_bottom.y) / 2}
|
||||
if not player.force.is_chunk_charted(surface, {math.floor(pos.x / 32), math.floor(pos.y / 32)}) then
|
||||
player.print("Cannot lock drop zone: area is not scanned!")
|
||||
else
|
||||
for _, jammer in pairs(surface.find_entities_filtered{
|
||||
area = {{pos.x - 120, pos.y - 120}, {pos.x + 120, pos.y + 120}},
|
||||
type = "radar"
|
||||
}) do
|
||||
if jammer.force ~= player.force and jammer.force.name ~= "neutral" then
|
||||
player.print("Cannot lock drop zone: area is jammed by an enemy!")
|
||||
if player.cursor_stack then player.cursor_stack.clear() end
|
||||
return
|
||||
end
|
||||
end
|
||||
storage.player_targets[player.force.name .. "_" .. surface.name] = {position = pos}
|
||||
player.print("Drop Zone LOCKED for " .. surface.name .. " at [" .. math.floor(pos.x) .. ", " .. math.floor(pos.y) .. "]")
|
||||
end
|
||||
if player.cursor_stack then player.cursor_stack.clear() end
|
||||
end
|
||||
|
||||
return drop_zone
|
||||
11
info.json
Normal file
11
info.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Planet-PvP",
|
||||
"version": "1.2.1",
|
||||
"title": "Planet PvP",
|
||||
"author": "SpRiTz708",
|
||||
"factorio_version": "2.1",
|
||||
"dependencies": [
|
||||
"base >= 2.0.77",
|
||||
"space-age"],
|
||||
"description": "A PvP mod for factorio where you can choose a planet of the first 4. Aquilo will be available in a future version."
|
||||
}
|
||||
28
locale/en/locale.cfg
Normal file
28
locale/en/locale.cfg
Normal file
@ -0,0 +1,28 @@
|
||||
[entity-name]
|
||||
pvp-base=Base
|
||||
|
||||
[item-name]
|
||||
pvp-base-building=Base
|
||||
pvp-base-core=Base Core
|
||||
|
||||
[recipe-name]
|
||||
pvp-base-repair=Repair Base
|
||||
|
||||
[technology-name]
|
||||
planet-discovery-nauvis=Planet discovery Nauvis
|
||||
|
||||
[technology-description]
|
||||
planet-discovery-nauvis=Chart a course to Nauvis, allowing space platforms to travel there.
|
||||
|
||||
[entity-description]
|
||||
pvp-base=The team base. As long as it stands your team can respawn. Destroy all enemy bases to eliminate them permanently.
|
||||
|
||||
[item-description]
|
||||
pvp-base-building=Place this to establish a team base.
|
||||
pvp-base-core=A destroyed base core. Craft it with repair packs to rebuild the base.
|
||||
|
||||
[recipe-description]
|
||||
pvp-base-repair=Repair a destroyed base core into a placeable base.
|
||||
|
||||
[controls]
|
||||
planet-pvp-toggle-observer=Toggle observer window
|
||||
570
observer.lua
Normal file
570
observer.lua
Normal file
@ -0,0 +1,570 @@
|
||||
local mod_gui = require("__core__.lualib.mod-gui")
|
||||
|
||||
local observer = {}
|
||||
|
||||
local PLANETS = {
|
||||
"nauvis",
|
||||
"vulcanus",
|
||||
"fulgora",
|
||||
"gleba",
|
||||
"aquilo"
|
||||
}
|
||||
|
||||
local function get_data(player)
|
||||
|
||||
storage.observers = storage.observers or {}
|
||||
|
||||
if not storage.observers[player.index] then
|
||||
storage.observers[player.index] = {
|
||||
following = nil,
|
||||
perspective = "observer",
|
||||
gui_open = false
|
||||
}
|
||||
end
|
||||
|
||||
return storage.observers[player.index]
|
||||
|
||||
end
|
||||
|
||||
local function is_observer(player)
|
||||
return player.controller_type == defines.controllers.spectator
|
||||
end
|
||||
|
||||
local function create_observer_force()
|
||||
|
||||
local force = game.forces["observer"]
|
||||
|
||||
if not force then
|
||||
force = game.create_force("observer")
|
||||
end
|
||||
|
||||
return force
|
||||
|
||||
end
|
||||
|
||||
observer.create_force = create_observer_force
|
||||
|
||||
function observer.create_button(player)
|
||||
|
||||
if not is_observer(player) then
|
||||
return
|
||||
end
|
||||
|
||||
local flow = mod_gui.get_button_flow(player)
|
||||
|
||||
if flow.observer_button then
|
||||
return
|
||||
end
|
||||
|
||||
flow.add{
|
||||
type = "sprite-button",
|
||||
name = "observer_button",
|
||||
sprite = "utility/search_icon",
|
||||
tooltip = "Observer"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function observer.destroy_button(player)
|
||||
|
||||
local flow = mod_gui.get_button_flow(player)
|
||||
|
||||
if flow.observer_button then
|
||||
flow.observer_button.destroy()
|
||||
end
|
||||
|
||||
if player.gui.screen.observer_window then
|
||||
player.gui.screen.observer_window.destroy()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function build_window(player)
|
||||
|
||||
local data = get_data(player)
|
||||
local selected_force_name = nil
|
||||
|
||||
if data.perspective
|
||||
and data.perspective ~= "observer"
|
||||
and storage.planet_teams then
|
||||
selected_force_name = storage.planet_teams[data.perspective]
|
||||
end
|
||||
|
||||
if player.gui.screen.observer_window then
|
||||
player.gui.screen.observer_window.destroy()
|
||||
end
|
||||
|
||||
local frame = player.gui.screen.add{
|
||||
type = "frame",
|
||||
name = "observer_window",
|
||||
direction = "vertical",
|
||||
caption = "Observer"
|
||||
}
|
||||
|
||||
frame.auto_center = true
|
||||
player.opened = frame
|
||||
|
||||
local perspective_header = frame.add{
|
||||
type = "flow",
|
||||
direction = "horizontal"
|
||||
}
|
||||
|
||||
perspective_header.add{
|
||||
type = "sprite",
|
||||
sprite = "utility/search_icon"
|
||||
}
|
||||
|
||||
perspective_header.add{
|
||||
type = "label",
|
||||
caption = "Perspective"
|
||||
}
|
||||
|
||||
local perspective_flow = frame.add{
|
||||
type = "flow",
|
||||
direction = "horizontal"
|
||||
}
|
||||
|
||||
for _, planet in ipairs(PLANETS) do
|
||||
perspective_flow.add{
|
||||
type = "sprite-button",
|
||||
name = "observer_perspective_" .. planet,
|
||||
sprite = "space-location/" .. planet,
|
||||
tooltip = planet:gsub("^%l", string.upper),
|
||||
style = "slot_button",
|
||||
toggled = data.perspective == planet
|
||||
}
|
||||
end
|
||||
|
||||
frame.add{
|
||||
type = "line"
|
||||
}
|
||||
|
||||
local perspective_caption = "None selected"
|
||||
|
||||
if selected_force_name then
|
||||
perspective_caption = selected_force_name
|
||||
end
|
||||
|
||||
frame.add{
|
||||
type = "label",
|
||||
caption = "Viewing: " .. perspective_caption
|
||||
}
|
||||
|
||||
frame.add{
|
||||
type = "line"
|
||||
}
|
||||
|
||||
local platform_header = frame.add{
|
||||
type = "flow",
|
||||
direction = "horizontal"
|
||||
}
|
||||
|
||||
platform_header.add{
|
||||
type = "sprite",
|
||||
sprite = "item/space-platform-foundation"
|
||||
}
|
||||
|
||||
platform_header.add{
|
||||
type = "label",
|
||||
caption = "Team Platforms"
|
||||
}
|
||||
|
||||
local platform_scroll = frame.add{
|
||||
type = "scroll-pane",
|
||||
name = "observer_platforms"
|
||||
}
|
||||
|
||||
platform_scroll.style.maximal_height = 220
|
||||
|
||||
local platforms_found = 0
|
||||
|
||||
if selected_force_name then
|
||||
for _, surface in pairs(game.surfaces) do
|
||||
local platform = surface.platform
|
||||
|
||||
if platform
|
||||
and platform.valid
|
||||
and platform.force
|
||||
and platform.force.name == selected_force_name then
|
||||
|
||||
local caption =
|
||||
"[img=item/space-platform-foundation] "
|
||||
.. platform.name
|
||||
|
||||
if platform.space_location then
|
||||
caption = caption
|
||||
.. " (" .. platform.space_location.name .. ")"
|
||||
elseif platform.space_connection then
|
||||
caption = caption .. " (traveling)"
|
||||
end
|
||||
|
||||
platform_scroll.add{
|
||||
type = "button",
|
||||
name = "observer_platform_" .. platform.index,
|
||||
caption = caption
|
||||
}
|
||||
|
||||
platforms_found = platforms_found + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not selected_force_name then
|
||||
platform_scroll.add{
|
||||
type = "label",
|
||||
caption = "Select a team perspective."
|
||||
}
|
||||
elseif platforms_found == 0 then
|
||||
platform_scroll.add{
|
||||
type = "label",
|
||||
caption = "No platforms owned by this team."
|
||||
}
|
||||
end
|
||||
|
||||
frame.add{
|
||||
type = "line"
|
||||
}
|
||||
|
||||
local player_header = frame.add{
|
||||
type = "flow",
|
||||
direction = "horizontal"
|
||||
}
|
||||
|
||||
player_header.add{
|
||||
type = "sprite",
|
||||
sprite = "entity/character"
|
||||
}
|
||||
|
||||
player_header.add{
|
||||
type = "label",
|
||||
caption = "Team Players"
|
||||
}
|
||||
|
||||
local scroll = frame.add{
|
||||
type = "scroll-pane",
|
||||
name = "observer_players"
|
||||
}
|
||||
|
||||
scroll.style.maximal_height = 320
|
||||
|
||||
local players_found = 0
|
||||
|
||||
for _, target in pairs(game.connected_players) do
|
||||
|
||||
local target_is_observer =
|
||||
storage.observers
|
||||
and storage.observers[target.index] ~= nil
|
||||
|
||||
local belongs_to_selected_team =
|
||||
selected_force_name
|
||||
and target.force
|
||||
and target.force.name == selected_force_name
|
||||
|
||||
if belongs_to_selected_team and not target_is_observer then
|
||||
|
||||
local caption = "[img=entity/character] " .. target.name
|
||||
|
||||
if not target.character then
|
||||
caption = caption .. " (dead)"
|
||||
elseif target.surface and target.surface.platform then
|
||||
caption = caption .. " (platform)"
|
||||
elseif target.surface then
|
||||
caption = caption .. " (" .. target.surface.name .. ")"
|
||||
end
|
||||
|
||||
scroll.add{
|
||||
type = "button",
|
||||
name = "observer_follow_" .. target.index,
|
||||
caption = caption
|
||||
}
|
||||
|
||||
players_found = players_found + 1
|
||||
end
|
||||
end
|
||||
|
||||
if not selected_force_name then
|
||||
scroll.add{
|
||||
type = "label",
|
||||
caption = "Select a team perspective."
|
||||
}
|
||||
elseif players_found == 0 then
|
||||
scroll.add{
|
||||
type = "label",
|
||||
caption = "No connected non-observer players on this team."
|
||||
}
|
||||
end
|
||||
|
||||
frame.add{
|
||||
type = "line"
|
||||
}
|
||||
|
||||
frame.add{
|
||||
type = "button",
|
||||
name = "observer_stop_follow",
|
||||
caption = "[img=utility/close] Stop Following"
|
||||
}
|
||||
|
||||
data.gui_open = true
|
||||
end
|
||||
|
||||
function observer.toggle_window(player)
|
||||
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
if not is_observer(player) then
|
||||
return
|
||||
end
|
||||
|
||||
local data = get_data(player)
|
||||
local window = player.gui.screen.observer_window
|
||||
|
||||
if window and window.valid then
|
||||
|
||||
if player.opened == window then
|
||||
player.opened = nil
|
||||
end
|
||||
|
||||
window.destroy()
|
||||
data.gui_open = false
|
||||
else
|
||||
build_window(player)
|
||||
end
|
||||
end
|
||||
|
||||
function observer.on_gui_click(event)
|
||||
|
||||
local player = game.get_player(event.player_index)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
if not is_observer(player) then
|
||||
return false
|
||||
end
|
||||
|
||||
local element = event.element
|
||||
if not (element and element.valid) then
|
||||
return false
|
||||
end
|
||||
|
||||
local data = get_data(player)
|
||||
|
||||
if element.name == "observer_button" then
|
||||
|
||||
if player.gui.screen.observer_window then
|
||||
player.gui.screen.observer_window.destroy()
|
||||
data.gui_open = false
|
||||
else
|
||||
build_window(player)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if element.name:find("^observer_perspective_") then
|
||||
|
||||
local perspective =
|
||||
element.name:gsub("^observer_perspective_", "")
|
||||
|
||||
local force_name =
|
||||
storage.planet_teams
|
||||
and storage.planet_teams[perspective]
|
||||
|
||||
local force =
|
||||
force_name
|
||||
and game.forces[force_name]
|
||||
|
||||
if force then
|
||||
player.force = force
|
||||
data.perspective = perspective
|
||||
data.following = nil
|
||||
|
||||
local surface = game.surfaces[perspective]
|
||||
|
||||
if surface then
|
||||
player.teleport({0, 0}, surface)
|
||||
end
|
||||
|
||||
player.print("Perspective: " .. force.name)
|
||||
|
||||
build_window(player)
|
||||
else
|
||||
player.print(
|
||||
"Force not found for perspective: "
|
||||
.. tostring(perspective)
|
||||
)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if element.name:find("^observer_follow_") then
|
||||
|
||||
local id = tonumber(element.name:match("%d+"))
|
||||
|
||||
if id then
|
||||
local target = game.get_player(id)
|
||||
|
||||
if target and target.valid and target.character and target.character.valid then
|
||||
data.following = id
|
||||
player.centered_on = target.character
|
||||
player.print("Now following " .. target.name)
|
||||
else
|
||||
player.print("That player is not currently available to follow.")
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if element.name:find("^observer_platform_") then
|
||||
|
||||
local platform_index =
|
||||
tonumber(element.name:match("%d+"))
|
||||
|
||||
local selected_platform = nil
|
||||
|
||||
if platform_index then
|
||||
for _, surface in pairs(game.surfaces) do
|
||||
local platform = surface.platform
|
||||
|
||||
if platform
|
||||
and platform.valid
|
||||
and platform.index == platform_index then
|
||||
selected_platform = platform
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if selected_platform
|
||||
and selected_platform.surface
|
||||
and selected_platform.surface.valid then
|
||||
|
||||
data.following = nil
|
||||
|
||||
player.teleport(
|
||||
{0, 0},
|
||||
selected_platform.surface
|
||||
)
|
||||
|
||||
player.print(
|
||||
"Viewing platform: "
|
||||
.. selected_platform.name
|
||||
)
|
||||
else
|
||||
player.print("Platform is no longer available.")
|
||||
build_window(player)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if element.name == "observer_stop_follow" then
|
||||
|
||||
data.following = nil
|
||||
player.print("Stopped following.")
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function observer.on_tick(event)
|
||||
|
||||
if not storage.observers then
|
||||
return
|
||||
end
|
||||
|
||||
for player_index, data in pairs(storage.observers) do
|
||||
|
||||
local observer_player = game.get_player(player_index)
|
||||
|
||||
if observer_player
|
||||
and observer_player.valid
|
||||
and observer_player.controller_type == defines.controllers.spectator
|
||||
and data.following then
|
||||
|
||||
local target = game.get_player(data.following)
|
||||
|
||||
if target
|
||||
and target.valid
|
||||
and target.connected
|
||||
and target.character then
|
||||
|
||||
if observer_player.surface ~= target.surface then
|
||||
|
||||
observer_player.teleport(
|
||||
target.position,
|
||||
target.surface
|
||||
)
|
||||
|
||||
else
|
||||
|
||||
local dx = observer_player.position.x - target.position.x
|
||||
local dy = observer_player.position.y - target.position.y
|
||||
|
||||
if math.abs(dx) > 2
|
||||
or math.abs(dy) > 2 then
|
||||
|
||||
observer_player.teleport(
|
||||
target.position,
|
||||
target.surface
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
data.following = nil
|
||||
|
||||
observer_player.print("Target unavailable.")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function observer.enable(player)
|
||||
|
||||
local force = create_observer_force()
|
||||
|
||||
player.force = force
|
||||
|
||||
player.set_controller{
|
||||
type = defines.controllers.spectator
|
||||
}
|
||||
|
||||
player.print("Perspective: " .. force.name)
|
||||
|
||||
player.show_on_map = false
|
||||
|
||||
player.set_controller{
|
||||
type = defines.controllers.spectator
|
||||
}
|
||||
|
||||
get_data(player)
|
||||
|
||||
observer.create_button(player)
|
||||
|
||||
end
|
||||
|
||||
function observer.disable(player)
|
||||
|
||||
observer.destroy_button(player)
|
||||
|
||||
if storage.observers then
|
||||
storage.observers[player.index] = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return observer
|
||||
50
player.lua
Normal file
50
player.lua
Normal file
@ -0,0 +1,50 @@
|
||||
local player_system = {}
|
||||
|
||||
local function hide_dot(player)
|
||||
if player and player.valid then player.show_on_map = false end
|
||||
end
|
||||
|
||||
local function maintain_light(player)
|
||||
if not (player and player.valid) then return end
|
||||
storage.player_lights = storage.player_lights or {}
|
||||
local light = storage.player_lights[player.index]
|
||||
if not player.character or not player.character.valid then
|
||||
if light and light.valid then light.destroy() end
|
||||
storage.player_lights[player.index] = nil
|
||||
return
|
||||
end
|
||||
if not light or not light.valid then
|
||||
storage.player_lights[player.index] = rendering.draw_light{sprite = "utility/light_medium", target = player.character, surface = player.character.surface, players = {player}, scale = 1.2, intensity = .8, minimum_darkness = .2}
|
||||
end
|
||||
end
|
||||
|
||||
function player_system.on_init()
|
||||
storage.player_lights = storage.player_lights or {}
|
||||
end
|
||||
|
||||
function player_system.on_player_created(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
hide_dot(player); maintain_light(player)
|
||||
end
|
||||
|
||||
function player_system.on_player_joined_game(event) hide_dot(game.get_player(event.player_index)) end
|
||||
function player_system.on_player_respawned(event) hide_dot(game.get_player(event.player_index)); maintain_light(game.get_player(event.player_index)) end
|
||||
|
||||
function player_system.on_player_changed_surface(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
if player and storage.player_lights[player.index] then
|
||||
local light = storage.player_lights[player.index]
|
||||
if light and light.valid then light.destroy() end
|
||||
storage.player_lights[player.index] = nil
|
||||
end
|
||||
maintain_light(player)
|
||||
end
|
||||
|
||||
function player_system.on_light_tick()
|
||||
for _, player in pairs(game.connected_players) do
|
||||
maintain_light(player)
|
||||
if player.character and player.character.valid then player.character.disable_flashlight() end
|
||||
end
|
||||
end
|
||||
|
||||
return player_system
|
||||
102
radar.lua
Normal file
102
radar.lua
Normal file
@ -0,0 +1,102 @@
|
||||
local radar = {}
|
||||
|
||||
local GRID_SIZE = 16
|
||||
local MAX_RADIUS = 3200
|
||||
|
||||
local function scan_orbit(entity, planet_name)
|
||||
local surface = game.planets[planet_name] and game.planets[planet_name].surface
|
||||
if not surface then return end
|
||||
|
||||
storage.grid_pos = storage.grid_pos or {}
|
||||
local scan_key = entity.force.name .. "_" .. planet_name
|
||||
if not storage.grid_pos[scan_key] then
|
||||
storage.grid_pos[scan_key] = {x = 0, y = 0, d = 0, step_limit = 1, steps_taken = 0, iteration = 0}
|
||||
end
|
||||
|
||||
local state = storage.grid_pos[scan_key]
|
||||
local position = {x = state.x, y = state.y}
|
||||
local blocked = false
|
||||
for _, jammer in pairs(surface.find_entities_filtered{
|
||||
area = {{position.x - 120, position.y - 120}, {position.x + 120, position.y + 120}},
|
||||
type = "radar"
|
||||
}) do
|
||||
if jammer.force ~= entity.force and jammer.force.name ~= "neutral" then
|
||||
blocked = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local scan_range = 1
|
||||
if not blocked then
|
||||
surface.request_to_generate_chunks(position, scan_range)
|
||||
entity.force.chart(surface, {
|
||||
{position.x - (GRID_SIZE * scan_range), position.y - (GRID_SIZE * scan_range)},
|
||||
{position.x + (GRID_SIZE * scan_range), position.y + (GRID_SIZE * scan_range)}
|
||||
})
|
||||
end
|
||||
|
||||
if state.d == 0 then state.x = state.x + GRID_SIZE * scan_range * 2
|
||||
elseif state.d == 1 then state.y = state.y + GRID_SIZE * scan_range * 2
|
||||
elseif state.d == 2 then state.x = state.x - GRID_SIZE * scan_range * 2
|
||||
else state.y = state.y - GRID_SIZE * scan_range * 2 end
|
||||
|
||||
state.steps_taken = state.steps_taken + 1
|
||||
if state.steps_taken >= state.step_limit then
|
||||
state.steps_taken = 0
|
||||
state.d = (state.d + 1) % 4
|
||||
state.iteration = state.iteration + 1
|
||||
if state.iteration % 2 == 0 then state.step_limit = state.step_limit + 1 end
|
||||
end
|
||||
if math.abs(state.x) > MAX_RADIUS or math.abs(state.y) > MAX_RADIUS then
|
||||
storage.grid_pos[scan_key] = {x = 0, y = 0, d = 0, step_limit = 1, steps_taken = 0, iteration = 0}
|
||||
end
|
||||
end
|
||||
|
||||
function radar.on_orbital_scan_tick()
|
||||
for _, surface in pairs(game.surfaces) do
|
||||
local platform = surface.platform
|
||||
local location = platform and platform.space_location
|
||||
if location and game.planets[location.name] then
|
||||
for _, entity in pairs(surface.find_entities_filtered{type = "radar"}) do
|
||||
if entity.valid and entity.active then
|
||||
scan_orbit(entity, location.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function radar.on_built_entity(event)
|
||||
local entity = event.entity
|
||||
if not (entity and entity.valid) or entity.type ~= "radar" or entity.surface.platform then return end
|
||||
|
||||
local jammer_force, jammer_pos, jammer_radius = entity.force, entity.position, 120
|
||||
for _, enemy_force in pairs(game.forces) do
|
||||
if enemy_force.name ~= jammer_force.name and enemy_force.name ~= "neutral" then
|
||||
local min_x = math.floor((jammer_pos.x - jammer_radius) / 32)
|
||||
local min_y = math.floor((jammer_pos.y - jammer_radius) / 32)
|
||||
local max_x = math.ceil((jammer_pos.x + jammer_radius) / 32)
|
||||
local max_y = math.ceil((jammer_pos.y + jammer_radius) / 32)
|
||||
for x = min_x, max_x do
|
||||
for y = min_y, max_y do enemy_force.unchart_chunk({x = x, y = y}, entity.surface) end
|
||||
end
|
||||
|
||||
storage.player_targets = storage.player_targets or {}
|
||||
local key = enemy_force.name .. "_" .. entity.surface.name
|
||||
local target = storage.player_targets[key]
|
||||
if target then
|
||||
local dx, dy = math.abs(target.position.x - jammer_pos.x), math.abs(target.position.y - jammer_pos.y)
|
||||
-- Jam the same reach on each axis, rather than a circular radius.
|
||||
if dx <= jammer_radius and dy <= jammer_radius then
|
||||
storage.player_targets[key] = nil
|
||||
local message = "[color=red][JAMMER] Your drop zone at [" .. math.floor(target.position.x)
|
||||
.. ", " .. math.floor(target.position.y) .. "] on " .. entity.surface.name
|
||||
.. " has been JAMMED by " .. jammer_force.name .. "![/color]"
|
||||
for _, player in pairs(enemy_force.players) do if player.valid then player.print(message) end end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return radar
|
||||
298
setup.lua
Normal file
298
setup.lua
Normal file
@ -0,0 +1,298 @@
|
||||
local base = require("base")
|
||||
local drop_zone = require("drop_zone")
|
||||
local setup = {}
|
||||
|
||||
local PLANETS = {"nauvis", "vulcanus", "fulgora", "gleba"}
|
||||
local HUB_SURFACE_NAME = "planet-pvp-hub"
|
||||
local HUB_MIN = -50
|
||||
local HUB_MAX = 49
|
||||
local HUB_VOID_MIN = -80
|
||||
local HUB_VOID_MAX = 79
|
||||
local HUB_LAYOUT_VERSION = 2
|
||||
local LOADOUTS = {
|
||||
nauvis = {{name = "burner-mining-drill", count = 1}, {name = "stone-furnace", count = 1}, {name = "iron-plate", count = 8}},
|
||||
vulcanus = {{name = "burner-mining-drill", count = 1}, {name = "stone-furnace", count = 1}, {name = "iron-plate", count = 8}},
|
||||
fulgora = {{name = "burner-mining-drill", count = 1}, {name = "stone-furnace", count = 1}, {name = "iron-plate", count = 8}},
|
||||
gleba = {{name = "burner-mining-drill", count = 1}, {name = "stone-furnace", count = 1}, {name = "iron-plate", count = 8}},
|
||||
}
|
||||
local UNIVERSAL = { ["automation-science-pack"] = true, ["logistic-science-pack"] = true, ["military-science-pack"] = true, ["chemical-science-pack"] = true, ["production-science-pack"] = true, ["utility-science-pack"] = true, ["space-science-pack"] = true }
|
||||
local TRIGGERS = {
|
||||
["planet-discovery-nauvis"] = "nauvis", ["planet-discovery-vulcanus"] = "vulcanus", ["planet-discovery-fulgora"] = "fulgora", ["planet-discovery-gleba"] = "gleba", ["planet-discovery-aquilo"] = "aquilo",
|
||||
["uranium-mining"] = "nauvis", ["bitter-egg-handling"] = "nauvis", ["recycling"] = "fulgora", ["holmium-processing"] = "fulgora", ["electromagnetic-plant"] = "fulgora", ["electromagnetic-science-pack"] = "fulgora",
|
||||
["jellynut"] = "gleba", ["yumako"] = "gleba", ["heating-tower"] = "gleba", ["agriculture"] = "gleba", ["biochamber"] = "gleba", ["artificial-soil"] = "gleba", ["bioflux"] = "gleba", ["bacteria-cultivation"] = "gleba", ["bioflux-processing"] = "gleba", ["agricultural-science-pack"] = "gleba",
|
||||
["tungsten-carbide"] = "vulcanus", ["calcite-processing"] = "vulcanus", ["foundry"] = "vulcanus", ["big-mining-drill"] = "vulcanus", ["tungsten-steel"] = "vulcanus", ["metallurgic-science-pack"] = "vulcanus",
|
||||
["lithium-processing"] = "aquilo", ["cryogenic-plant"] = "aquilo", ["cryogenic-science-pack"] = "aquilo",
|
||||
}
|
||||
local BASIC = {["steam-power"] = true, ["electronics"] = true, ["automation-science-pack"] = true, ["steel-axe"] = true, ["oil-processing"] = true}
|
||||
|
||||
local function protect_hub_walls(surface)
|
||||
for _, wall in pairs(surface.find_entities_filtered{name = "stone-wall", area = {{HUB_MIN - 1, HUB_MIN - 1}, {HUB_MAX + 1, HUB_MAX + 1}}}) do
|
||||
wall.destructible = false
|
||||
wall.minable_flag = false
|
||||
end
|
||||
end
|
||||
|
||||
local function build_hub_layout(surface)
|
||||
surface.request_to_generate_chunks({0, 0}, 2)
|
||||
surface.force_generate_chunk_requests()
|
||||
|
||||
local tiles = {}
|
||||
for x = HUB_VOID_MIN, HUB_VOID_MAX do
|
||||
for y = HUB_VOID_MIN, HUB_VOID_MAX do
|
||||
tiles[#tiles + 1] = {name = "out-of-map", position = {x, y}}
|
||||
end
|
||||
end
|
||||
for x = HUB_MIN, HUB_MAX do
|
||||
for y = HUB_MIN, HUB_MAX do
|
||||
tiles[#tiles + 1] = {name = "refined-concrete", position = {x, y}}
|
||||
end
|
||||
end
|
||||
surface.set_tiles(tiles, true)
|
||||
|
||||
-- Replace any older lobby border before placing the updated perimeter.
|
||||
for _, wall in pairs(surface.find_entities_filtered{name = "stone-wall", area = {{HUB_VOID_MIN, HUB_VOID_MIN}, {HUB_VOID_MAX + 1, HUB_VOID_MAX + 1}}}) do
|
||||
wall.destroy()
|
||||
end
|
||||
local function create_wall(position)
|
||||
local wall = surface.create_entity{name = "stone-wall", position = position, force = game.forces.player}
|
||||
if wall then wall.destructible = false; wall.minable_flag = false end
|
||||
end
|
||||
for x = HUB_MIN, HUB_MAX do
|
||||
create_wall({x, HUB_MIN})
|
||||
create_wall({x, HUB_MAX})
|
||||
end
|
||||
for y = HUB_MIN + 1, HUB_MAX - 1 do
|
||||
create_wall({HUB_MIN, y})
|
||||
create_wall({HUB_MAX, y})
|
||||
end
|
||||
storage.hub_layout_version = HUB_LAYOUT_VERSION
|
||||
end
|
||||
|
||||
local function get_hub_surface()
|
||||
local surface = game.surfaces[HUB_SURFACE_NAME]
|
||||
if not surface then
|
||||
surface = game.create_surface(HUB_SURFACE_NAME, {
|
||||
seed = 7082026,
|
||||
peaceful_mode = true,
|
||||
autoplace_controls = {},
|
||||
autoplace_settings = {
|
||||
entity = {treat_missing_as_default = false, settings = {}},
|
||||
tile = {treat_missing_as_default = false, settings = {}},
|
||||
},
|
||||
})
|
||||
end
|
||||
if storage.hub_layout_version ~= HUB_LAYOUT_VERSION then
|
||||
build_hub_layout(surface)
|
||||
else
|
||||
protect_hub_walls(surface)
|
||||
end
|
||||
return surface
|
||||
end
|
||||
|
||||
local function teleport_to_hub(player)
|
||||
if not (player and player.valid) or player.controller_type == defines.controllers.spectator then return end
|
||||
local surface = get_hub_surface()
|
||||
if player.controller_type == defines.controllers.cutscene then player.exit_cutscene() end
|
||||
local position = surface.find_non_colliding_position("character", {0, 0}, 48, 1) or {0, 0}
|
||||
player.teleport(position, surface)
|
||||
storage.player_surfaces[player.index] = surface.index
|
||||
end
|
||||
|
||||
local function has_selected_team(player)
|
||||
if storage.player_team_joined and storage.player_team_joined[player.index] then return true end
|
||||
for _, force_name in pairs(storage.planet_teams or {}) do
|
||||
if player.force and player.force.name == force_name then
|
||||
storage.player_team_joined = storage.player_team_joined or {}
|
||||
storage.player_team_joined[player.index] = true
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function assign_fulgora_attractors(surface, area)
|
||||
local force_name = storage.planet_teams and storage.planet_teams.fulgora
|
||||
local force = force_name and game.forces[force_name]
|
||||
if not (surface and surface.valid and force) then return end
|
||||
for _, attractor in pairs(surface.find_entities_filtered{
|
||||
name = "fulgoran-ruin-attractor",
|
||||
area = area,
|
||||
}) do
|
||||
attractor.force = force
|
||||
end
|
||||
end
|
||||
|
||||
local function set_pvp_relations()
|
||||
for i, a_name in ipairs(PLANETS) do
|
||||
local a = game.forces[a_name]
|
||||
if a then for j, b_name in ipairs(PLANETS) do
|
||||
local b = game.forces[b_name]
|
||||
if i ~= j and b then a.set_friend(b, false); b.set_friend(a, false); a.set_cease_fire(b, false); b.set_cease_fire(a, false) end
|
||||
end end
|
||||
end
|
||||
end
|
||||
|
||||
local function requirement(tech)
|
||||
local found
|
||||
for _, ingredient in pairs(tech.research_unit_ingredients) do
|
||||
if not UNIVERSAL[ingredient.name] then if found and found ~= ingredient.name then return "mixed" end; found = ingredient.name end
|
||||
end
|
||||
return found or "base"
|
||||
end
|
||||
|
||||
local function unlock(force, planet_name)
|
||||
force.unlock_space_location(planet_name)
|
||||
for _, tech in pairs(force.technologies) do
|
||||
if tech.enabled then
|
||||
local assigned = TRIGGERS[tech.name]
|
||||
local unlock_tech = BASIC[tech.name] or (requirement(tech) == "base" and not tech.prototype.upgrade)
|
||||
if assigned then unlock_tech = assigned == planet_name and not tech.prototype.upgrade end
|
||||
if tech.name == "metallurgic-science-pack" or tech.name == "electromagnetic-science-pack" or tech.name == "agricultural-science-pack" or tech.name == "cryogenic-science-pack" then unlock_tech = false end
|
||||
if (tech.name:find("productivity%-module") or tech.name:find("speed%-module") or tech.name:find("efficiency%-module") or tech.name:find("quality%-module")) and tech.prototype.max_level < 3 then unlock_tech = true end
|
||||
tech.researched = unlock_tech
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function show_admin(player)
|
||||
if player.gui.screen.pvp_admin_frame then player.gui.screen.pvp_admin_frame.destroy() end
|
||||
local frame = player.gui.screen.add{type = "frame", name = "pvp_admin_frame", caption = "ADMIN: CONFIGURE PLANET TEAMS", direction = "vertical"}
|
||||
frame.auto_center = true
|
||||
local table_ui = frame.add{type = "table", column_count = 2, name = "setup_table"}
|
||||
for _, planet in pairs(PLANETS) do
|
||||
table_ui.add{type = "label", caption = planet:gsub("^%l", string.upper) .. " Force Name:"}
|
||||
table_ui.add{type = "textfield", name = "tf_" .. planet, text = "Team " .. planet:gsub("^%l", string.upper)}
|
||||
end
|
||||
frame.add{type = "button", name = "confirm_admin_setup", caption = "INITIALIZE WORLD", style = "confirm_button"}
|
||||
end
|
||||
|
||||
local function show_join(player)
|
||||
if player.gui.screen.pvp_join_frame then player.gui.screen.pvp_join_frame.destroy() end
|
||||
if not storage.game_started or not storage.planet_teams then return end
|
||||
local frame = player.gui.screen.add{type = "frame", name = "pvp_join_frame", caption = "CHOOSE YOUR HOME PLANET", direction = "vertical"}
|
||||
frame.auto_center = true
|
||||
for planet, force_name in pairs(storage.planet_teams) do frame.add{type = "button", name = "join_btn_" .. planet, caption = "Join " .. force_name .. " (" .. planet .. ")", style = "confirm_button"} end
|
||||
end
|
||||
|
||||
local function show_waiting(player)
|
||||
if player.gui.screen.pvp_waiting_frame then player.gui.screen.pvp_waiting_frame.destroy() end
|
||||
local frame = player.gui.screen.add{type = "frame", name = "pvp_waiting_frame", caption = "PLANET PvP LOBBY", direction = "vertical"}
|
||||
frame.auto_center = true
|
||||
frame.add{type = "label", caption = "The admin is setting up the game. You can choose a team when setup is complete."}
|
||||
end
|
||||
|
||||
function setup.on_init(observer)
|
||||
storage.planet_teams, storage.player_targets, storage.grid_pos = {}, {}, {}
|
||||
storage.game_started, storage.team_kit_spawned, storage.player_surfaces = false, {}, {}
|
||||
storage.player_team_joined = {}
|
||||
storage.force_bases, storage.permadead, storage.pending_spectator, storage.force_colors, storage.base_label_ids = {}, {}, {}, {}, {}
|
||||
storage.player_lights, storage.crash_site_cleanup_done = {}, false
|
||||
observer.create_force()
|
||||
set_pvp_relations()
|
||||
get_hub_surface()
|
||||
for _, player in pairs(game.players) do player.set_shortcut_available("lock_drop_zone_shortcut", true) end
|
||||
end
|
||||
|
||||
function setup.on_configuration_changed()
|
||||
for _, key in pairs({"team_kit_spawned", "player_surfaces", "force_bases", "permadead", "pending_spectator", "force_colors", "base_label_ids", "player_targets", "grid_pos", "player_lights", "player_team_joined"}) do storage[key] = storage[key] or {} end
|
||||
get_hub_surface()
|
||||
for planet_name, force_name in pairs(storage.planet_teams or {}) do
|
||||
local force = game.forces[force_name]
|
||||
if force then
|
||||
force.unlock_space_location(planet_name)
|
||||
local tech = force.technologies["planet-discovery-nauvis"]
|
||||
if tech and planet_name == "nauvis" then tech.researched = true end
|
||||
end
|
||||
end
|
||||
local fulgora = game.planets.fulgora
|
||||
if fulgora and fulgora.surface then assign_fulgora_attractors(fulgora.surface) end
|
||||
for _, player in pairs(game.players) do player.set_shortcut_available("lock_drop_zone_shortcut", true) end
|
||||
for _, player in pairs(game.connected_players) do
|
||||
if not has_selected_team(player) then
|
||||
teleport_to_hub(player)
|
||||
if storage.game_started then show_join(player) elseif player.admin then show_admin(player) else show_waiting(player) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function setup.on_chunk_generated(event)
|
||||
if event.surface and event.surface.name == "fulgora" then
|
||||
assign_fulgora_attractors(event.surface, event.area)
|
||||
end
|
||||
end
|
||||
|
||||
function setup.on_player_created(event)
|
||||
local player = game.players[event.player_index]
|
||||
storage.player_team_joined = storage.player_team_joined or {}
|
||||
player.set_shortcut_available("lock_drop_zone_shortcut", true)
|
||||
if player.controller_type == defines.controllers.cutscene then player.exit_cutscene() end
|
||||
teleport_to_hub(player)
|
||||
if storage.game_started then show_join(player) elseif player.admin then show_admin(player) else show_waiting(player) end
|
||||
end
|
||||
|
||||
function setup.on_player_joined_game(event)
|
||||
local player = game.players[event.player_index]
|
||||
if not (player and player.valid) then return end
|
||||
storage.player_team_joined = storage.player_team_joined or {}
|
||||
if has_selected_team(player) or (storage.observers and storage.observers[player.index]) then return end
|
||||
teleport_to_hub(player)
|
||||
if storage.game_started then show_join(player) elseif player.admin then show_admin(player) else show_waiting(player) end
|
||||
end
|
||||
|
||||
function setup.on_player_respawned(event)
|
||||
local player = game.players[event.player_index]
|
||||
if player and player.valid and not has_selected_team(player) then
|
||||
teleport_to_hub(player)
|
||||
end
|
||||
end
|
||||
|
||||
function setup.on_gui_click(event)
|
||||
local element, player = event.element, game.players[event.player_index]
|
||||
if not (element and element.valid and player and player.valid) then return end
|
||||
if element.name == "confirm_admin_setup" then
|
||||
storage.planet_teams = {}
|
||||
for _, planet in pairs(PLANETS) do
|
||||
local textfield = element.parent.setup_table["tf_" .. planet]
|
||||
local name = textfield and textfield.text ~= "" and textfield.text or "Team " .. planet
|
||||
if not game.forces[name] then game.create_force(name) end
|
||||
unlock(game.forces[name], planet); base.assign_force_color(name); storage.planet_teams[planet] = name
|
||||
end
|
||||
local fulgora = game.planets.fulgora
|
||||
if fulgora and fulgora.surface then assign_fulgora_attractors(fulgora.surface) end
|
||||
storage.game_started = true; element.parent.destroy()
|
||||
for _, target in pairs(game.players) do
|
||||
if target.valid and not (storage.player_team_joined and storage.player_team_joined[target.index]) then
|
||||
teleport_to_hub(target)
|
||||
if target.gui.screen.pvp_waiting_frame then target.gui.screen.pvp_waiting_frame.destroy() end
|
||||
show_join(target)
|
||||
end
|
||||
end
|
||||
elseif element.name:find("^join_btn_") then
|
||||
local planet_name = element.name:gsub("^join_btn_", "")
|
||||
local force_name = storage.planet_teams[planet_name]
|
||||
if force_name then
|
||||
player.force = game.forces[force_name]; player.clear_items_inside()
|
||||
storage.player_team_joined = storage.player_team_joined or {}
|
||||
storage.player_team_joined[player.index] = true
|
||||
local planet = game.planets[planet_name]
|
||||
if planet then
|
||||
local surface = planet.create_surface()
|
||||
if planet_name == "fulgora" then assign_fulgora_attractors(surface) end
|
||||
if not storage.team_kit_spawned[force_name] then
|
||||
storage.team_kit_spawned[force_name] = true
|
||||
local position = surface.find_non_colliding_position("steel-chest", {x = 5, y = 0}, 100, .5) or {x = 5, y = 0}
|
||||
local chest = surface.create_entity{name = "steel-chest", position = position, force = player.force, raise_built = true}
|
||||
if chest and chest.valid then
|
||||
local inventory = chest.get_inventory(defines.inventory.chest)
|
||||
if inventory then for _, item in pairs(LOADOUTS[planet_name]) do if prototypes.item[item.name] then inventory.insert(item) end end end
|
||||
end
|
||||
base.spawn_initial(force_name, planet_name)
|
||||
end
|
||||
drop_zone.teleport_to_planet(player, surface)
|
||||
end
|
||||
end
|
||||
if element.parent and element.parent.valid then element.parent.destroy() end
|
||||
end
|
||||
end
|
||||
|
||||
return setup
|
||||
BIN
thumbnail.png
Normal file
BIN
thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
Loading…
x
Reference in New Issue
Block a user