Compare commits
3 Commits
master
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ff8ba10a2 | |||
| 88f5211ddb | |||
| 0e9e38509e |
22
base.lua
22
base.lua
@ -29,7 +29,20 @@ function base.assign_force_color(force_name)
|
||||
storage.force_colors[force_name] = COLORS[(used % #COLORS) + 1]
|
||||
end
|
||||
|
||||
function base.register(entity)
|
||||
function base.player_color(player)
|
||||
if not (player and player.valid and player.force) then return end
|
||||
local force_name = player.force.name
|
||||
local is_team = false
|
||||
for _, name in pairs(storage.planet_teams or {}) do
|
||||
if name == force_name then is_team = true; break end
|
||||
end
|
||||
if not is_team then return end
|
||||
storage.force_colors = storage.force_colors or {}
|
||||
base.assign_force_color(force_name)
|
||||
player.color = storage.force_colors[force_name]
|
||||
end
|
||||
|
||||
function base.register(entity, silent)
|
||||
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 {}
|
||||
@ -40,8 +53,10 @@ function base.register(entity)
|
||||
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]")
|
||||
if not silent then
|
||||
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
|
||||
end
|
||||
|
||||
function base.spawn_initial(force_name, planet_name)
|
||||
@ -99,6 +114,7 @@ end
|
||||
function base.on_player_respawned(event)
|
||||
local player = game.players[event.player_index]
|
||||
if not (player and player.valid) then return end
|
||||
base.player_color(player)
|
||||
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
|
||||
|
||||
34
control.lua
34
control.lua
@ -4,17 +4,27 @@ local drop_zone = require("drop_zone")
|
||||
local base = require("base")
|
||||
local setup = require("setup")
|
||||
local player_system = require("player")
|
||||
local roster = require("roster")
|
||||
|
||||
-- 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_configuration_changed(function(event)
|
||||
setup.on_configuration_changed(event)
|
||||
for _, player in pairs(game.players) do
|
||||
roster.create_button(player)
|
||||
base.player_color(player)
|
||||
end
|
||||
end)
|
||||
|
||||
script.on_event(defines.events.on_player_created, function(event)
|
||||
player_system.on_player_created(event)
|
||||
roster.create_button(game.get_player(event.player_index))
|
||||
setup.on_player_created(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_player_joined_game, function(event)
|
||||
player_system.on_player_joined_game(event)
|
||||
roster.create_button(game.get_player(event.player_index))
|
||||
base.player_color(game.get_player(event.player_index))
|
||||
setup.on_player_joined_game(event)
|
||||
end)
|
||||
script.on_event(defines.events.on_player_respawned, function(event)
|
||||
@ -31,25 +41,41 @@ 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_robot_built_entity, radar.on_built_entity)
|
||||
script.on_nth_tick(60, radar.on_jam_tick)
|
||||
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)
|
||||
-- No writable "save loaded" event exists; a local flag on the permanent on_tick
|
||||
-- handler fires once per load. on_nth_tick(1) cannot be used: registering a
|
||||
-- transient handler makes join-time event checks fail for multiplayer clients.
|
||||
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
|
||||
if not (observer.on_gui_click(event) or roster.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)
|
||||
script.on_nth_tick(120, function()
|
||||
player_system.on_light_tick()
|
||||
for _, player in pairs(game.connected_players) do base.player_color(player) end
|
||||
end)
|
||||
local save_loaded_fired = false
|
||||
script.on_event(defines.events.on_tick, function(event)
|
||||
if not save_loaded_fired then
|
||||
save_loaded_fired = true
|
||||
setup.on_save_loaded()
|
||||
end
|
||||
base.on_tick(event)
|
||||
drop_zone.on_tick(event)
|
||||
end)
|
||||
|
||||
commands.add_command("basestatus", "Show base count for every force", base.status_command)
|
||||
commands.add_command("ping", nil, function(command)
|
||||
|
||||
9
data.lua
9
data.lua
@ -1,3 +1,8 @@
|
||||
-- Both mods define the same prototypes; give a clear error instead of duplicate-name crashes.
|
||||
if mods["Planet-PvP"] then
|
||||
error("Planet PvP Green cannot be enabled at the same time as the original Planet-PvP mod. Disable one of them.")
|
||||
end
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "selection-tool",
|
||||
@ -83,7 +88,7 @@ data:extend({
|
||||
{
|
||||
type = "item",
|
||||
name = "pvp-base-core",
|
||||
icon = "__Planet-PvP__/Graphics/Base/base_core.png",
|
||||
icon = "__Planet-PvP-Green__/Graphics/Base/base_core.png",
|
||||
icon_size = 128,
|
||||
subgroup = "other",
|
||||
order = "z[pvp-base-core]",
|
||||
@ -135,7 +140,7 @@ data:extend({
|
||||
{
|
||||
type = "technology",
|
||||
name = "planet-discovery-nauvis",
|
||||
icon = "__Planet-PvP__/Graphics/Tech/nauvis-tech.png",
|
||||
icon = "__Planet-PvP-Green__/Graphics/Tech/nauvis-tech.png",
|
||||
icon_size = 210,
|
||||
essential = true,
|
||||
effects = {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
local radar = require("radar")
|
||||
|
||||
local drop_zone = {}
|
||||
|
||||
function drop_zone.teleport_to_planet(player, surface)
|
||||
@ -6,23 +8,35 @@ function drop_zone.teleport_to_planet(player, surface)
|
||||
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)
|
||||
-- Retries live in storage and are pumped from the permanent on_tick;
|
||||
-- transient on_nth_tick registrations break multiplayer join checks.
|
||||
storage.pending_teleports = storage.pending_teleports or {}
|
||||
storage.pending_teleports[player.index] = {position = position, surface_index = surface.index, attempts = 0}
|
||||
end
|
||||
|
||||
function drop_zone.on_tick()
|
||||
local pending = storage.pending_teleports
|
||||
if not pending or not next(pending) then return end
|
||||
for index, data in pairs(pending) do
|
||||
local player = game.players[index]
|
||||
local surface = game.surfaces[data.surface_index]
|
||||
if player and player.valid and surface and data.attempts < 30 then
|
||||
player.teleport(data.position, surface)
|
||||
if player.character then player.set_controller{type = defines.controllers.character, character = player.character} end
|
||||
attempts = attempts + 1
|
||||
data.attempts = data.attempts + 1
|
||||
else
|
||||
script.on_nth_tick(1, nil)
|
||||
pending[index] = nil
|
||||
end
|
||||
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
|
||||
-- render_mode is only "game" when physically present; "chart"/"chart_zoomed_out"
|
||||
-- mean the surface is just being viewed on the map, so don't teleport.
|
||||
if event.surface_index == surface.index or surface.platform or player.render_mode ~= defines.render_mode.game 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
|
||||
@ -62,15 +76,10 @@ function drop_zone.on_player_selected_area(event)
|
||||
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
|
||||
if radar.sector_jammed(surface, pos, player.force) 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
|
||||
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) .. "]")
|
||||
|
||||
10
info.json
10
info.json
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "Planet-PvP",
|
||||
"version": "1.2.1",
|
||||
"title": "Planet PvP",
|
||||
"author": "SpRiTz708",
|
||||
"name": "Planet-PvP-Green",
|
||||
"version": "1.0.1",
|
||||
"title": "Planet PvP Green",
|
||||
"author": "Blizliam",
|
||||
"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."
|
||||
"description": "A PvP mod for factorio where you can choose a planet of the first 4."
|
||||
}
|
||||
|
||||
84
player.lua
84
player.lua
@ -18,6 +18,88 @@ local function maintain_light(player)
|
||||
end
|
||||
end
|
||||
|
||||
-- show_on_map is all-or-nothing, so allies are revealed with rendering objects
|
||||
-- whose players list is restricted to the owner's force instead.
|
||||
-- Name over the character in-world and on the map, plus a dot on the map.
|
||||
-- Each is separate because render_mode is per-object.
|
||||
local ALLY_MARKER_SPECS = {
|
||||
{key = "name_game", mode = "game", text = true},
|
||||
{key = "name_chart", mode = "chart", text = true},
|
||||
{key = "dot_chart", mode = "chart", radius = 2},
|
||||
}
|
||||
|
||||
local function is_team_force(force_name)
|
||||
for _, name in pairs(storage.planet_teams or {}) do
|
||||
if name == force_name then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function maintain_marker(player)
|
||||
if not (player and player.valid) then return end
|
||||
storage.ally_markers = storage.ally_markers or {}
|
||||
local markers = storage.ally_markers[player.index]
|
||||
local viewers = {}
|
||||
if player.character and player.character.valid and player.force and is_team_force(player.force.name) then
|
||||
for _, ally in pairs(player.force.players) do
|
||||
viewers[#viewers + 1] = ally.index
|
||||
end
|
||||
end
|
||||
if #viewers == 0 then
|
||||
if markers then for _, marker in pairs(markers) do if marker.valid then marker.destroy() end end end
|
||||
storage.ally_markers[player.index] = nil
|
||||
return
|
||||
end
|
||||
local color = (storage.force_colors and storage.force_colors[player.force.name]) or {r = 1, g = 1, b = 1, a = 1}
|
||||
markers = markers or {}
|
||||
for _, spec in pairs(ALLY_MARKER_SPECS) do
|
||||
local marker = markers[spec.key]
|
||||
if marker and marker.valid and marker.surface ~= player.character.surface then
|
||||
marker.destroy()
|
||||
marker = nil
|
||||
markers[spec.key] = nil
|
||||
end
|
||||
if not (marker and marker.valid) then
|
||||
if spec.text then
|
||||
marker = rendering.draw_text{
|
||||
text = player.name, surface = player.character.surface, target = player.character,
|
||||
target_offset = {0, -2.5}, color = color, scale = 1.5, render_mode = spec.mode, players = viewers,
|
||||
}
|
||||
else
|
||||
marker = rendering.draw_circle{
|
||||
surface = player.character.surface, target = player.character, color = color,
|
||||
radius = spec.radius, filled = true, render_mode = spec.mode, players = viewers,
|
||||
}
|
||||
end
|
||||
markers[spec.key] = marker
|
||||
else
|
||||
marker.players = viewers
|
||||
marker.target = player.character
|
||||
marker.color = color
|
||||
end
|
||||
end
|
||||
storage.ally_markers[player.index] = markers
|
||||
end
|
||||
|
||||
-- Vanilla pins can bind to a player or their character, leaking a live
|
||||
-- tracker arrow that defeats show_on_map = false. Drop any pin that targets
|
||||
-- a player/character of another force.
|
||||
local function clear_enemy_pins(player)
|
||||
for _, pin in pairs(player.get_pins()) do
|
||||
local pinned = pin.player
|
||||
local leak = pinned and pinned.valid and pinned.force ~= player.force
|
||||
if not leak then
|
||||
for _, target in pairs(pin.targets or {}) do
|
||||
if target.valid and target.type == "character" and target.force ~= player.force then
|
||||
leak = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if leak and pin.valid then pin.destroy() end
|
||||
end
|
||||
end
|
||||
|
||||
function player_system.on_init()
|
||||
storage.player_lights = storage.player_lights or {}
|
||||
end
|
||||
@ -43,6 +125,8 @@ end
|
||||
function player_system.on_light_tick()
|
||||
for _, player in pairs(game.connected_players) do
|
||||
maintain_light(player)
|
||||
maintain_marker(player)
|
||||
clear_enemy_pins(player)
|
||||
if player.character and player.character.valid then player.character.disable_flashlight() end
|
||||
end
|
||||
end
|
||||
|
||||
107
radar.lua
107
radar.lua
@ -3,6 +3,36 @@ local radar = {}
|
||||
local GRID_SIZE = 16
|
||||
local MAX_RADIUS = 3200
|
||||
|
||||
local function powered(entity)
|
||||
return entity.energy > 0
|
||||
end
|
||||
|
||||
-- Half-size of the square active-vision footprint, matching what the entity reveals on the map.
|
||||
local function vision_half_size(entity)
|
||||
if entity.type == "radar" then
|
||||
return (entity.prototype.max_distance_of_nearby_sector_revealed or 3) * 32
|
||||
end
|
||||
local cell = entity.logistic_cell
|
||||
return cell and cell.construction_radius or 0
|
||||
end
|
||||
|
||||
local function enemy_vision(entity, force)
|
||||
return entity.force ~= force and entity.force.name ~= "neutral"
|
||||
end
|
||||
|
||||
local function sector_jammed(surface, position, force)
|
||||
for _, source in pairs(surface.find_entities_filtered{type = {"radar", "roboport"}}) do
|
||||
if source.active and powered(source) and enemy_vision(source, force) then
|
||||
local half = vision_half_size(source)
|
||||
if math.abs(position.x - source.position.x) <= half and math.abs(position.y - source.position.y) <= half then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
radar.sector_jammed = sector_jammed
|
||||
|
||||
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
|
||||
@ -15,16 +45,7 @@ local function scan_orbit(entity, planet_name)
|
||||
|
||||
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 blocked = sector_jammed(surface, position, entity.force)
|
||||
|
||||
local scan_range = 1
|
||||
if not blocked then
|
||||
@ -52,14 +73,63 @@ local function scan_orbit(entity, planet_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Stored charge as a fraction of the buffer; drives how fast a radar accumulates scans.
|
||||
local function scan_power(entity)
|
||||
local buffer = entity.electric_buffer_size
|
||||
if not buffer or buffer == 0 then return 1 end
|
||||
return math.min(1, entity.energy / buffer)
|
||||
end
|
||||
|
||||
function radar.on_orbital_scan_tick()
|
||||
storage.radar_scan_progress = storage.radar_scan_progress or {}
|
||||
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)
|
||||
local progress = (storage.radar_scan_progress[entity.unit_number] or 0) + scan_power(entity)
|
||||
if progress >= 1 then
|
||||
progress = progress - 1
|
||||
scan_orbit(entity, location.name)
|
||||
end
|
||||
storage.radar_scan_progress[entity.unit_number] = progress
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Powered overlapping enemy vision squares jam each other. The engine cannot
|
||||
-- darken only the overlap, so an overlapped radar is disabled entirely until
|
||||
-- the other side dies, loses power, or moves out of range. Roboports count as
|
||||
-- vision sources but are never disabled themselves.
|
||||
function radar.on_jam_tick()
|
||||
storage.jammed_entities = storage.jammed_entities or {}
|
||||
for unit_number, entity in pairs(storage.jammed_entities) do
|
||||
if entity.valid then entity.active = true end
|
||||
storage.jammed_entities[unit_number] = nil
|
||||
end
|
||||
for _, surface in pairs(game.surfaces) do
|
||||
if not surface.platform then
|
||||
local sources = {}
|
||||
for _, entity in pairs(surface.find_entities_filtered{type = {"radar", "roboport"}}) do
|
||||
if entity.valid and entity.active and powered(entity) then sources[#sources + 1] = entity end
|
||||
end
|
||||
for i = 1, #sources do
|
||||
for j = i + 1, #sources do
|
||||
local a, b = sources[i], sources[j]
|
||||
if enemy_vision(a, b.force) then
|
||||
local reach = vision_half_size(a) + vision_half_size(b)
|
||||
if math.abs(a.position.x - b.position.x) <= reach and math.abs(a.position.y - b.position.y) <= reach then
|
||||
for _, entity in pairs({a, b}) do
|
||||
if entity.type == "radar" then
|
||||
entity.active = false
|
||||
storage.jammed_entities[entity.unit_number] = entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -70,24 +140,17 @@ 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
|
||||
-- A fresh vision source jams any enemy drop-zone lock inside its footprint.
|
||||
local jammer_force, jammer_pos = entity.force, entity.position
|
||||
local jammer_half = vision_half_size(entity)
|
||||
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
|
||||
if dx <= jammer_half and dy <= jammer_half 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
|
||||
|
||||
80
roster.lua
Normal file
80
roster.lua
Normal file
@ -0,0 +1,80 @@
|
||||
local mod_gui = require("__core__.lualib.mod-gui")
|
||||
|
||||
local roster = {}
|
||||
|
||||
local PLANETS = {"nauvis", "vulcanus", "fulgora", "gleba"}
|
||||
|
||||
local function format_playtime(ticks)
|
||||
local total_seconds = math.floor((ticks or 0) / 60)
|
||||
local hours = math.floor(total_seconds / 3600)
|
||||
local minutes = math.floor((total_seconds % 3600) / 60)
|
||||
return string.format("%d:%02d", hours, minutes)
|
||||
end
|
||||
|
||||
function roster.create_button(player)
|
||||
if not (player and player.valid) then return end
|
||||
local flow = mod_gui.get_button_flow(player)
|
||||
if flow.pvp_roster_button then return end
|
||||
flow.add{
|
||||
type = "sprite-button",
|
||||
name = "pvp_roster_button",
|
||||
sprite = "entity/character",
|
||||
tooltip = "Team roster",
|
||||
}
|
||||
end
|
||||
|
||||
local function build_window(player)
|
||||
if player.gui.screen.pvp_roster_frame then player.gui.screen.pvp_roster_frame.destroy() end
|
||||
local frame = player.gui.screen.add{type = "frame", name = "pvp_roster_frame", caption = "TEAM ROSTER", direction = "vertical"}
|
||||
frame.auto_center = true
|
||||
local any_team = false
|
||||
for _, planet in ipairs(PLANETS) do
|
||||
local force_name = storage.planet_teams and storage.planet_teams[planet]
|
||||
if force_name then
|
||||
any_team = true
|
||||
local color = storage.force_colors and storage.force_colors[force_name]
|
||||
local header = frame.add{type = "label", caption = force_name .. " (" .. planet .. ")", style = "caption_label"}
|
||||
if color then header.style.font_color = color end
|
||||
local found = false
|
||||
for _, target in pairs(game.players) do
|
||||
if target.force and target.force.name == force_name then
|
||||
found = true
|
||||
local status = target.connected and "" or " (offline)"
|
||||
local line = frame.add{type = "label", caption = " " .. target.name .. status .. " - " .. format_playtime(target.online_time)}
|
||||
if color then line.style.font_color = color end
|
||||
end
|
||||
end
|
||||
if not found then frame.add{type = "label", caption = " (no players)"} end
|
||||
end
|
||||
end
|
||||
if not any_team then frame.add{type = "label", caption = "No teams initialized yet."} end
|
||||
local buttons = frame.add{type = "flow", direction = "horizontal"}
|
||||
buttons.add{type = "button", name = "pvp_roster_refresh", caption = "Refresh"}
|
||||
buttons.add{type = "button", name = "pvp_roster_close", caption = "Close"}
|
||||
end
|
||||
|
||||
function roster.on_gui_click(event)
|
||||
local element = event.element
|
||||
if not (element and element.valid) then return false end
|
||||
local player = game.get_player(event.player_index)
|
||||
if not (player and player.valid) then return false end
|
||||
if element.name == "pvp_roster_button" then
|
||||
if player.gui.screen.pvp_roster_frame then
|
||||
player.gui.screen.pvp_roster_frame.destroy()
|
||||
else
|
||||
build_window(player)
|
||||
end
|
||||
return true
|
||||
end
|
||||
if element.name == "pvp_roster_refresh" then
|
||||
build_window(player)
|
||||
return true
|
||||
end
|
||||
if element.name == "pvp_roster_close" then
|
||||
if player.gui.screen.pvp_roster_frame then player.gui.screen.pvp_roster_frame.destroy() end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return roster
|
||||
128
setup.lua
128
setup.lua
@ -1,5 +1,6 @@
|
||||
local base = require("base")
|
||||
local drop_zone = require("drop_zone")
|
||||
local observer = require("observer")
|
||||
local setup = {}
|
||||
|
||||
local PLANETS = {"nauvis", "vulcanus", "fulgora", "gleba"}
|
||||
@ -154,6 +155,109 @@ local function unlock(force, planet_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Surfaces are cheap until chunks get requested; create them all so orbital
|
||||
-- radar can scout planets before any player has landed.
|
||||
local function init_planet_surfaces()
|
||||
for _, planet_name in ipairs(PLANETS) do
|
||||
local planet = game.planets[planet_name]
|
||||
if planet then planet.create_surface() end
|
||||
end
|
||||
end
|
||||
|
||||
local RESERVED_FORCES = {player = true, enemy = true, neutral = true, observer = true}
|
||||
|
||||
-- storage is per-mod, so a Planet-PvP save loses everything on the swap. Team
|
||||
-- forces persist in the save itself; find which force owns a planet via its
|
||||
-- surviving base, then its researched discovery tech, then unlocked locations.
|
||||
local function find_team_force(planet_name)
|
||||
local planet = game.planets[planet_name]
|
||||
local surface = planet and planet.surface
|
||||
if surface then
|
||||
for _, entity in pairs(surface.find_entities_filtered{name = "pvp-base"}) do
|
||||
if entity.valid and not RESERVED_FORCES[entity.force.name] then return entity.force.name end
|
||||
end
|
||||
end
|
||||
local tech_name = "planet-discovery-" .. planet_name
|
||||
for _, force in pairs(game.forces) do
|
||||
if not RESERVED_FORCES[force.name] then
|
||||
local tech = force.technologies[tech_name]
|
||||
if tech and tech.researched then return force.name end
|
||||
end
|
||||
end
|
||||
-- Nauvis can be unlocked by default, so only trust a force whose single
|
||||
-- unlocked planet of the four is the one being matched.
|
||||
for _, force in pairs(game.forces) do
|
||||
if not RESERVED_FORCES[force.name] then
|
||||
local unlocked = {}
|
||||
for _, p in ipairs(PLANETS) do
|
||||
if force.is_space_location_unlocked(p) then unlocked[#unlocked + 1] = p end
|
||||
end
|
||||
if #unlocked == 1 and unlocked[1] == planet_name then return force.name end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function migrate_from_planet_pvp()
|
||||
for _, planet_name in ipairs(PLANETS) do
|
||||
if not storage.planet_teams[planet_name] then
|
||||
local force_name = find_team_force(planet_name)
|
||||
if force_name then
|
||||
storage.planet_teams[planet_name] = force_name
|
||||
base.assign_force_color(force_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
local team_forces = {}
|
||||
for _, force_name in pairs(storage.planet_teams) do team_forces[force_name] = true end
|
||||
for planet_name, force_name in pairs(storage.planet_teams) do
|
||||
local planet = game.planets[planet_name]
|
||||
if planet and planet.surface then
|
||||
for _, entity in pairs(planet.surface.find_entities_filtered{name = "pvp-base", force = force_name}) do
|
||||
if entity.valid then
|
||||
base.register(entity, true)
|
||||
storage.team_kit_spawned[force_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, player in pairs(game.players) do
|
||||
if player.force and team_forces[player.force.name] then
|
||||
storage.team_kit_spawned[player.force.name] = true
|
||||
storage.player_team_joined[player.index] = true
|
||||
base.player_color(player)
|
||||
end
|
||||
if player.controller_type == defines.controllers.spectator and player.force.name == "observer" then
|
||||
storage.observers[player.index] = {following = nil, perspective = "observer", gui_open = false}
|
||||
end
|
||||
end
|
||||
if next(storage.planet_teams) then
|
||||
storage.game_started = true
|
||||
storage.crash_site_cleanup_done = true
|
||||
local names = {}
|
||||
for force_name in pairs(team_forces) do names[#names + 1] = force_name end
|
||||
for i, a_name in ipairs(names) do
|
||||
for j, b_name in ipairs(names) do
|
||||
if i ~= j then
|
||||
local a, b = game.forces[a_name], game.forces[b_name]
|
||||
if a and b then a.set_friend(b, false); a.set_cease_fire(b, false) end
|
||||
end
|
||||
end
|
||||
end
|
||||
for planet_name, force_name in pairs(storage.planet_teams) do
|
||||
local members = {}
|
||||
for _, player in pairs(game.players) do
|
||||
if player.force and player.force.name == force_name then members[#members + 1] = player.name end
|
||||
end
|
||||
game.print("[Planet PvP] " .. planet_name .. " = " .. force_name .. " (" .. #members .. "): " .. table.concat(members, ", "))
|
||||
end
|
||||
end
|
||||
for _, player in pairs(game.players) do
|
||||
if player.force and not team_forces[player.force.name] and not RESERVED_FORCES[player.force.name] then
|
||||
game.print("[Planet PvP] WARNING: " .. player.name .. " is on unknown force '" .. player.force.name .. "'. They were sent to the lobby to pick a team.")
|
||||
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"}
|
||||
@ -193,9 +297,14 @@ function setup.on_init(observer)
|
||||
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
|
||||
function setup.on_configuration_changed(event)
|
||||
if event and event.mod_changes and event.mod_changes["Planet-PvP"] then
|
||||
game.print("Save migrated from Planet-PvP to Planet PvP Green. All teams, bases and drop zones carry over.")
|
||||
end
|
||||
for _, key in pairs({"planet_teams", "team_kit_spawned", "player_surfaces", "force_bases", "permadead", "pending_spectator", "force_colors", "base_label_ids", "player_targets", "grid_pos", "player_lights", "player_team_joined", "observers", "radar_scan_progress", "ally_markers", "pending_teleports"}) do storage[key] = storage[key] or {} end
|
||||
if event and event.mod_changes and event.mod_changes["Planet-PvP"] then migrate_from_planet_pvp() end
|
||||
get_hub_surface()
|
||||
if storage.game_started then init_planet_surfaces() end
|
||||
for planet_name, force_name in pairs(storage.planet_teams or {}) do
|
||||
local force = game.forces[force_name]
|
||||
if force then
|
||||
@ -206,7 +315,11 @@ function setup.on_configuration_changed()
|
||||
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.players) do
|
||||
player.set_shortcut_available("lock_drop_zone_shortcut", true)
|
||||
-- Restore observer UI for spectators whose button may not have survived a mod swap.
|
||||
if player.controller_type == defines.controllers.spectator then observer.create_button(player) end
|
||||
end
|
||||
for _, player in pairs(game.connected_players) do
|
||||
if not has_selected_team(player) then
|
||||
teleport_to_hub(player)
|
||||
@ -215,6 +328,13 @@ function setup.on_configuration_changed()
|
||||
end
|
||||
end
|
||||
|
||||
-- No writable "save loaded" event exists, so control.lua fires this via a
|
||||
-- one-shot nth_tick(1). Covers saves hosted from before surfaces were
|
||||
-- initialized at game start, where on_configuration_changed may not run.
|
||||
function setup.on_save_loaded()
|
||||
if storage.game_started then init_planet_surfaces() end
|
||||
end
|
||||
|
||||
function setup.on_chunk_generated(event)
|
||||
if event.surface and event.surface.name == "fulgora" then
|
||||
assign_fulgora_attractors(event.surface, event.area)
|
||||
@ -257,6 +377,7 @@ function setup.on_gui_click(event)
|
||||
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
|
||||
init_planet_surfaces()
|
||||
local fulgora = game.planets.fulgora
|
||||
if fulgora and fulgora.surface then assign_fulgora_attractors(fulgora.surface) end
|
||||
storage.game_started = true; element.parent.destroy()
|
||||
@ -272,6 +393,7 @@ function setup.on_gui_click(event)
|
||||
local force_name = storage.planet_teams[planet_name]
|
||||
if force_name then
|
||||
player.force = game.forces[force_name]; player.clear_items_inside()
|
||||
base.player_color(player)
|
||||
storage.player_team_joined = storage.player_team_joined or {}
|
||||
storage.player_team_joined[player.index] = true
|
||||
local planet = game.planets[planet_name]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user