diff --git a/maps/scrap_towny_ffa/limited_radar.lua b/maps/scrap_towny_ffa/limited_radar.lua index dd00616b..2300e1b3 100644 --- a/maps/scrap_towny_ffa/limited_radar.lua +++ b/maps/scrap_towny_ffa/limited_radar.lua @@ -59,9 +59,9 @@ local function on_chunk_charted(event) update_forces(health_text) add_force(health_text, force.name) -- protection text - local zone_text = town_center.zone_text - update_forces(zone_text) - add_force(zone_text, force.name) + local shield_text = town_center.shield_text + update_forces(shield_text) + add_force(shield_text, force.name) end end diff --git a/maps/scrap_towny_ffa/market.lua b/maps/scrap_towny_ffa/market.lua index 498cad8f..6429499b 100644 --- a/maps/scrap_towny_ffa/market.lua +++ b/maps/scrap_towny_ffa/market.lua @@ -2,6 +2,7 @@ local table_insert = table.insert local ScenarioTable = require 'maps.scrap_towny_ffa.table' local Town_center = require 'maps.scrap_towny_ffa.town_center' +local PvPShield = require 'maps.scrap_towny_ffa.pvp_shield' local upgrade_functions = { -- Upgrade Town Center Health @@ -86,6 +87,33 @@ local upgrade_functions = { this.spawn_point[player.index] = spawn_point surface.play_sound({path = 'utility/scenario_message', position = player.position, volume_modifier = 1}) return false + end, + -- Pause-mode PvP Shield + [8] = function(town_center, player) + local this = ScenarioTable.get_table() + local market = town_center.market + local force = market.force + local surface = market.surface + local shield_lifetime_ticks = 10 * 60 * 60 + + if not this.pvp_shields[player.force.name] then + -- Double-check with the player to prevent accidental clicks + if this.pvp_shield_warned[player.force.name] ~= nil and game.tick - this.pvp_shield_warned[player.force.name] < 60 * 60 then + if not Town_center.enemy_players_nearby(town_center, 100) then + PvPShield.add_shield(surface, force, market.position, shield_lifetime_ticks, 2 * 60 * 60, true) + surface.play_sound({path = 'utility/scenario_message', position = player.position, volume_modifier = 1}) + this.pvp_shield_warned[player.force.name] = nil + else + player.print("Enemy players are too close, can't deploy PvP shield") + end + else + player.force.print('You have requested a temporary PvP shield. This will freeze all players in your town for ' .. string.format("%.0f", shield_lifetime_ticks / 60 / 60) .. ' minutes to take a break. Click again to confirm.') + this.pvp_shield_warned[player.force.name] = game.tick + end + else + player.print("Your town already has a PvP shield") + end + return false end } @@ -134,6 +162,7 @@ local function set_offers(town_center) special_offers[6] = {{{'coin', (town_center.upgrades.laser_turret.slots * 150)}}, laser_turret} local spawn_point = 'Set Spawn Point' special_offers[7] = {{}, spawn_point} + special_offers[8] = {{}, 'Temporary PvP Shield for pause/AFK'} for _, v in pairs(special_offers) do table_insert(market_items, {price = v[1], offer = {type = 'nothing', effect_description = v[2]}}) end diff --git a/maps/scrap_towny_ffa/pvp_shield.lua b/maps/scrap_towny_ffa/pvp_shield.lua index 9bd23acf..7ff810ea 100644 --- a/maps/scrap_towny_ffa/pvp_shield.lua +++ b/maps/scrap_towny_ffa/pvp_shield.lua @@ -1,20 +1,22 @@ local Public = {} +local math_sqrt = math.sqrt + local Event = require 'utils.event' local ScenarioTable = require 'maps.scrap_towny_ffa.table' local CommonFunctions = require 'utils.common' local max_size = 120 local beam_type = 'electric-beam-no-sound' -local max_lifetime_ticks = 4 * 60 * 60 * 60 -local time_to_full_size_ticks = 60 * 60 +local default_lifetime_ticks = 2 * 60 * 60 * 60 +local default_time_to_full_size_ticks = 60 * 60 -local function draw_borders(zone) - local surface = zone.surface - local right = zone.box.right_bottom.x - local left = zone.box.left_top.x - local top = zone.box.left_top.y - local bottom = zone.box.right_bottom.y +local function draw_borders(shield) + local surface = shield.surface + local right = shield.box.right_bottom.x + local left = shield.box.left_top.x + local top = shield.box.left_top.y + local bottom = shield.box.right_bottom.y surface.create_entity({name = beam_type, position = {right, top}, source = {right, top}, target = {right, bottom + 0.5}}) -- intentional offset here to correct visual appearance @@ -26,56 +28,73 @@ local function draw_borders(zone) source = {left, top - 0.5}, target = {right, top}}) end -local function remove_drawn_borders(zone) - for _, e in pairs(zone.surface.find_entities_filtered({area = zone.box, name = beam_type})) do +local function remove_drawn_borders(shield) + for _, e in pairs(shield.surface.find_entities_filtered({area = shield.box, name = beam_type})) do if e.valid then e.destroy() end end end -local function scale_size_and_box(zone) - local time_scale = math.min(1, (game.tick - zone.lifetime_start) / time_to_full_size_ticks) +local function scale_size_and_box(shield) + local time_scale = math.min(1, (game.tick - shield.lifetime_start) / shield.time_to_full_size_ticks) local scaled_size = time_scale * max_size - local center = zone.center + local center = shield.center local box = {left_top = { x = center.x - scaled_size / 2, y = center.y - scaled_size / 2}, right_bottom = { x = center.x + scaled_size / 2, y = center.y + scaled_size / 2}} - zone.box = box - zone.size = scaled_size + shield.box = box + shield.size = scaled_size end -function Public.add_zone(surface, force, center) +function Public.add_shield(surface, force, center, lifetime_ticks, time_to_full_size_ticks, is_pause_mode) local this = ScenarioTable.get_table() - local zone = {surface = surface, force = force, center = center, lifetime_start = game.tick} - scale_size_and_box(zone) - this.pvp_shields[force.name] = zone + if not lifetime_ticks then + lifetime_ticks = default_lifetime_ticks + end + if not time_to_full_size_ticks then + time_to_full_size_ticks = default_time_to_full_size_ticks + end + + local shield = {surface = surface, force = force, center = center, max_lifetime_ticks = lifetime_ticks, + time_to_full_size_ticks = time_to_full_size_ticks, lifetime_start = game.tick, is_pause_mode = is_pause_mode} + + if is_pause_mode then + -- Freeze players to avoid AFK abuse + shield.force.character_running_speed_modifier = -1 + game.print("Your AFK PvP shield is now rolling out. You will be frozen until it expires in " .. + string.format("%.0f", (Public.remaining_lifetime(shield)) / 60 / 60) .. ' minutes') + end + + scale_size_and_box(shield) + this.pvp_shields[force.name] = shield end -function Public.remove_zone(zone) +function Public.remove_shield(shield) local this = ScenarioTable.get_table() - remove_drawn_borders(zone) - this.pvp_shields[zone.force.name] = nil - zone.force.print("Your PvP Shield has expired", {r = 1, g = 0, b = 0}) + remove_drawn_borders(shield) + + if shield.is_pause_mode then + shield.force.character_running_speed_modifier = 0 + end + + this.pvp_shields[shield.force.name] = nil + shield.force.print("Your PvP Shield has expired", {r = 1, g = 0, b = 0}) end -function Public.remaining_lifetime(zone) - return max_lifetime_ticks - (game.tick - zone.lifetime_start) +function Public.remaining_lifetime(shield) + return shield.max_lifetime_ticks - (game.tick - shield.lifetime_start) end -local function vector_norm(vector) - return math.sqrt(vector.x ^ 2 + vector.y ^ 2) -end - -local function update_zone_lifetime() +local function update_shield_lifetime() local this = ScenarioTable.get_table() - for _, zone in pairs(this.pvp_shields) do - if Public.remaining_lifetime(zone) > 0 then - if zone.size < max_size then - remove_drawn_borders(zone) - scale_size_and_box(zone) - draw_borders(zone) + for _, shield in pairs(this.pvp_shields) do + if Public.remaining_lifetime(shield) > 0 then + if shield.size < max_size then + remove_drawn_borders(shield) + scale_size_and_box(shield) + draw_borders(shield) -- Push everyone out as we grow (even if they're just standing) for _, player in pairs(game.connected_players) do @@ -83,19 +102,23 @@ local function update_zone_lifetime() end end else - Public.remove_zone(zone) + Public.remove_shield(shield) end end end +local function vector_norm(vector) + return math_sqrt(vector.x ^ 2 + vector.y ^ 2) +end + function Public.push_enemies_out(player) local this = ScenarioTable.get_table() - for _, zone in pairs(this.pvp_shields) do - if not (zone.force == player.force or zone.force.get_friend(player.force) or player.surface ~= zone.surface) then - if CommonFunctions.point_in_bounding_box(player.position, zone.box) then + for _, shield in pairs(this.pvp_shields) do + if not (shield.force == player.force or shield.force.get_friend(player.force) or player.surface ~= shield.surface) then + if CommonFunctions.point_in_bounding_box(player.position, shield.box) then if player.character then -- Push player away from center - local center_diff = { x = player.position.x - zone.center.x, y = player.position.y - zone.center.y} + local center_diff = { x = player.position.x - shield.center.x, y = player.position.y - shield.center.y} center_diff.x = center_diff.x / vector_norm(center_diff) center_diff.y = center_diff.y / vector_norm(center_diff) player.teleport({ player.position.x + center_diff.x, player.position.y + center_diff.y}, player.surface) @@ -128,6 +151,6 @@ local function on_player_changed_position(event) end Event.add(defines.events.on_player_changed_position, on_player_changed_position) -Event.on_nth_tick(60, update_zone_lifetime) +Event.on_nth_tick(60, update_shield_lifetime) return Public diff --git a/maps/scrap_towny_ffa/table.lua b/maps/scrap_towny_ffa/table.lua index 52b5a71a..c4949dfb 100644 --- a/maps/scrap_towny_ffa/table.lua +++ b/maps/scrap_towny_ffa/table.lua @@ -38,6 +38,7 @@ function Public.reset_table() this.mining_target = {} this.spaceships = {} this.pvp_shields = {} + this.pvp_shield_warned = {} end function Public.get_table() diff --git a/maps/scrap_towny_ffa/team.lua b/maps/scrap_towny_ffa/team.lua index fc8d7997..97f85a3d 100644 --- a/maps/scrap_towny_ffa/team.lua +++ b/maps/scrap_towny_ffa/team.lua @@ -826,7 +826,7 @@ local function kill_force(force_name, cause) end end if this.pvp_shields[force_name] then - PvPShield.remove_zone(this.pvp_shields[force_name]) + PvPShield.remove_shield(this.pvp_shields[force_name]) end game.merge_forces(force_name, 'neutral') this.town_centers[force_name] = nil diff --git a/maps/scrap_towny_ffa/town_center.lua b/maps/scrap_towny_ffa/town_center.lua index a501cb49..a5926084 100644 --- a/maps/scrap_towny_ffa/town_center.lua +++ b/maps/scrap_towny_ffa/town_center.lua @@ -3,6 +3,7 @@ local Public = {} local math_random = math.random local table_insert = table.insert local math_floor = math.floor +local math_sqrt = math.sqrt local table_shuffle = table.shuffle_table local Event = require 'utils.event' @@ -368,17 +369,35 @@ function Public.update_coin_balance(force) rendering.set_text(town_center.coins_text, 'Coins: ' .. town_center.coin_balance) end -local function update_protection_display() +function Public.enemy_players_nearby(town_center, max_radius) + local own_force = town_center.market.force + local town_position = town_center.market.position + + for _, player in pairs(game.connected_players) do + if player.surface == town_center.market.surface then + local distance = math_floor(math_sqrt((player.position.x - town_position.x) ^ 2 + + (player.position.y - town_position.y) ^ 2)) + if distance < max_radius then + if player.force ~= "enemy" and (own_force ~= player.force and not own_force.get_friend(player.force)) then + return true + end + end + end + end + return false +end + +local function update_pvp_shields_display() local this = ScenarioTable.get_table() for _, town_center in pairs(this.town_centers) do - local zone = this.pvp_shields[town_center.market.force.name] + local shield = this.pvp_shields[town_center.market.force.name] local info - if zone then - info = 'PvP Shield: ' .. string.format("%.0f", (PvPShield.remaining_lifetime(zone)) / 60 / 60) .. ' minutes' + if shield then + info = 'PvP Shield: ' .. string.format("%.0f", (PvPShield.remaining_lifetime(shield)) / 60 / 60) .. ' minutes' else info = '' end - rendering.set_text(town_center.zone_text, info) + rendering.set_text(town_center.shield_text, info) end end @@ -528,7 +547,7 @@ local function found_town(event) scale_with_zoom = false } - town_center.zone_text = rendering.draw_text { + town_center.shield_text = rendering.draw_text { text = 'PvP Shield: (..)', surface = surface, forces = {force_name, game.forces.player, game.forces.rogue}, @@ -545,8 +564,8 @@ local function found_town(event) Enemy.clear_enemies(position, surface, town_radius * 5) draw_town_spawn(force_name) - PvPShield.add_zone(surface, force, { x = position.x + 0.5, y = position.y + 0.5}) -- Market center is slightly shifted - update_protection_display() + PvPShield.add_shield(surface, force, { x = position.x + 0.5, y = position.y + 0.5}) -- Market center is slightly shifted + update_pvp_shields_display() -- set the spawn point local pos = {x = town_center.market.position.x, y = town_center.market.position.y + 4} @@ -634,7 +653,7 @@ commands.add_command( Event.add(defines.events.on_built_entity, on_built_entity) Event.add(defines.events.on_player_repaired_entity, on_player_repaired_entity) -Event.on_nth_tick(60, update_protection_display) +Event.on_nth_tick(60, update_pvp_shields_display) --Event.add(defines.events.on_robot_repaired_entity, on_robot_repaired_entity) Event.add(defines.events.on_entity_damaged, on_entity_damaged)