TheBiky88

events.lua

Feb 25th, 2026 (edited)
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 39.22 KB | None | 0 0
  1. -- ============================================================================
  2. -- CONFIGURATION
  3. -- ============================================================================
  4.  
  5. local DEBUG_MODE = false
  6.  
  7. local DEBRIS_CONFIG = {
  8.     surfaces = {"nauvis"},
  9.     fragments = {
  10.         {"debris-c", 40},
  11.         {"debris-b", 30},
  12.         {"debris-a", 30},
  13.     },
  14.     max_debris = 80,
  15.     min_interval = 60 * 60 * 10,
  16.     max_interval = 60 * 60 * 20,
  17.     spawn_radius = 300,
  18.     scatter_radius = 10,
  19.     min_fragments = 8,
  20.     max_fragments = 16,
  21.     bonus_loot_chance = 0.1,
  22.     bonus_loot_table = {
  23.         {"productivity-module-3", 1, 1},
  24.         {"speed-module-3", 1, 2},
  25.         {"efficiency-module-3", 1, 3},
  26.         {"cliff-explosives", 2, 3},
  27.     },
  28.     tag_cleanup_interval = 60 * 60 * 15,
  29.     tag_check_radius = 20
  30. }
  31.  
  32. local POLLUTION_CONFIG = {
  33.     surfaces = {"eneas"},
  34.     check_interval = 60 * 60 * 15,
  35.     check_radius = 200,
  36.     vent_pollution_min = 6000,
  37.     vent_pollution_max = 12000,
  38.     vent_search_radius = 1000,
  39.     thresholds = {
  40.         low = 500,
  41.         medium = 1000,
  42.         high = 2500,
  43.         critical = 5000
  44.     }
  45. }
  46.  
  47. local SUPPLY_REWARDS = {
  48.     base = {
  49.         ["ancient-material"] = {min = 50, max = 100},
  50.         ["ancient-circuit"]  = {min = 50, max = 100},
  51.         ["ancient-frame"]    = {min = 30, max = 60},
  52.     },
  53.     early_memory = {
  54.         tech_required = "supply-tech-1",
  55.         items = {["ancient-core"] = {min = 5, max = 5}},
  56.     },
  57.     mid_memory = {
  58.         tech_required = "codex-tech",
  59.         items = {["notable-codex"] = {min = 5, max = 10}},
  60.     },
  61.     late_memory = {
  62.         tech_required = "oil-tech",
  63.         items = {["heavy-oil-barrel"] = {min = 20, max = 35}},
  64.     }
  65. }
  66.  
  67. local YIELD_BOOST_TECHS = {
  68.     ["supply-tech-1"]  = 1.25,
  69.     ["supply-tech-2"]  = 1.5,
  70.     ["supply-techs-3"] = 2.0,
  71. }
  72.  
  73. local POLLUTION_REWARD_MULTIPLIERS = {
  74.     clean    = 2.0,
  75.     low      = 1.5,
  76.     medium   = 1.0,
  77.     high     = 0.5,
  78.     critical = 0.0
  79. }
  80.  
  81. -- ============================================================================
  82. -- STORAGE INITIALIZATION
  83. -- ============================================================================
  84.  
  85. function init_debris()
  86.     storage.events = storage.events or {}
  87.  
  88.     if not storage.events.debris then
  89.         storage.events.debris = {
  90.             enabled = true,
  91.             -- FIX: storage.rng.debris is used here instead of math.random().
  92.             -- But the generators are created just below, so we initialise
  93.             -- events.debris *after* we know the generators exist.
  94.             -- We defer the next_tick assignment to after rng init below.
  95.         }
  96.     end
  97.  
  98.     if not storage.events.pollution then
  99.         storage.events.pollution = {
  100.             enabled = true,
  101.             next_tick = game.tick + POLLUTION_CONFIG.check_interval
  102.         }
  103.     end
  104.  
  105.     if not storage.events.tag_cleanup then
  106.         storage.events.tag_cleanup = {
  107.             enabled = true,
  108.             next_tick = game.tick + DEBRIS_CONFIG.tag_cleanup_interval
  109.         }
  110.     end
  111.  
  112.     -- -----------------------------------------------------------------------
  113.     -- FIX: Create deterministic random generators and store them in `storage`.
  114.     --
  115.     -- game.create_random_generator() produces a LuaRandomGenerator seeded from
  116.     -- the map's own random state, which is identical on every client because it
  117.     -- comes from the synchronized game seed — not from the local system clock.
  118.     --
  119.     -- We store each generator in storage so that:
  120.     --   1. Its internal state is included in the save file.
  121.     --   2. A player joining mid-game loads the save and gets the same generator
  122.     --      state the server has, keeping their RNG in sync going forward.
  123.     --
  124.     -- We create one generator per system so their sequences are independent.
  125.     -- The `or` guards ensure we only create them once; on_configuration_changed
  126.     -- calls init_debris() again but should not reset existing generators.
  127.     -- -----------------------------------------------------------------------
  128.     storage.rng = storage.rng or {}
  129.     storage.rng.debris    = storage.rng.debris    or game.create_random_generator()
  130.     storage.rng.pollution = storage.rng.pollution or game.create_random_generator()
  131.     storage.rng.loot      = storage.rng.loot      or game.create_random_generator()
  132.     storage.rng.talk      = storage.rng.talk      or game.create_random_generator()
  133.     storage.rng.spawn     = storage.rng.spawn     or game.create_random_generator()
  134.  
  135.     -- Now that the rng exists, assign the first debris next_tick if not set yet.
  136.     if not storage.events.debris.next_tick then
  137.         -- FIX: storage.rng.debris(min, max) instead of math.random(min, max)
  138.         storage.events.debris.next_tick = game.tick +
  139.             storage.rng.debris(DEBRIS_CONFIG.min_interval, DEBRIS_CONFIG.max_interval)
  140.     end
  141. end
  142.  
  143. -- ============================================================================
  144. -- DEBRIS SYSTEM
  145. -- ============================================================================
  146.  
  147. local function count_debris_on_surface(surface)
  148.     local total = 0
  149.     for i = 1, #DEBRIS_CONFIG.fragments do
  150.         local debris_name = DEBRIS_CONFIG.fragments[i][1]
  151.         total = total + #surface.find_entities_filtered{name = debris_name}
  152.     end
  153.     return total
  154. end
  155.  
  156. local function select_random_debris()
  157.     local total_weight = 0
  158.     for i = 1, #DEBRIS_CONFIG.fragments do
  159.         total_weight = total_weight + DEBRIS_CONFIG.fragments[i][2]
  160.     end
  161.  
  162.     -- FIX: storage.rng.debris() instead of math.random()
  163.     -- storage.rng.debris() with no arguments returns a float in [0,1),
  164.     -- matching the old math.random() behaviour used here.
  165.     local rand = storage.rng.debris() * total_weight
  166.     local cumulative = 0
  167.  
  168.     for i = 1, #DEBRIS_CONFIG.fragments do
  169.         cumulative = cumulative + DEBRIS_CONFIG.fragments[i][2]
  170.         if rand <= cumulative then
  171.             return DEBRIS_CONFIG.fragments[i][1]
  172.         end
  173.     end
  174.  
  175.     return DEBRIS_CONFIG.fragments[1][1]
  176. end
  177.  
  178. local function find_valid_spawn_location(surface, max_attempts)
  179.     max_attempts = max_attempts or 50
  180.  
  181.     for attempt = 1, max_attempts do
  182.         -- FIX: storage.rng.debris(min, max) instead of math.random(min, max)
  183.         local x = storage.rng.debris(-DEBRIS_CONFIG.spawn_radius, DEBRIS_CONFIG.spawn_radius)
  184.         local y = storage.rng.debris(-DEBRIS_CONFIG.spawn_radius, DEBRIS_CONFIG.spawn_radius)
  185.         local position = {x = x, y = y}
  186.  
  187.         local area = {
  188.             {x - DEBRIS_CONFIG.scatter_radius, y - DEBRIS_CONFIG.scatter_radius},
  189.             {x + DEBRIS_CONFIG.scatter_radius, y + DEBRIS_CONFIG.scatter_radius}
  190.         }
  191.  
  192.         if not surface.can_place_entity{name = DEBRIS_CONFIG.fragments[1][1], position = position} then
  193.             goto continue
  194.         end
  195.  
  196.         local cliffs = surface.find_entities_filtered{area = area, type = "cliff"}
  197.         if #cliffs > 0 then goto continue end
  198.  
  199.         local entities = surface.find_entities_filtered{area = area}
  200.         local has_blocking_entity = false
  201.  
  202.         for i = 1, #entities do
  203.             local entity = entities[i]
  204.             local t = entity.type
  205.             if t ~= "tree" and t ~= "simple-entity" and t ~= "decorative"
  206.                and t ~= "resource" and t ~= "fish" then
  207.                 has_blocking_entity = true
  208.                 break
  209.             end
  210.         end
  211.  
  212.         if not has_blocking_entity then
  213.             return position
  214.         end
  215.  
  216.         ::continue::
  217.     end
  218.  
  219.     return nil
  220. end
  221.  
  222. local function create_crash_effects(surface, position)
  223.     surface.create_entity{name = "explosion", position = position}
  224.     surface.play_sound{path = "utility/build_large", position = position, volume_modifier = 1.0}
  225. end
  226.  
  227. local function scatter_fragments(surface, crash_position, fragment_count)
  228.     local placed_positions = {}
  229.     local fragments_placed = 0
  230.  
  231.     for attempt = 1, fragment_count * 5 do
  232.         if fragments_placed >= fragment_count then break end
  233.  
  234.         -- FIX: storage.rng.debris() instead of math.random()
  235.         local angle    = storage.rng.debris() * 2 * math.pi
  236.         local distance = storage.rng.debris() * DEBRIS_CONFIG.scatter_radius
  237.         local fragment_pos = {
  238.             x = crash_position.x + math.cos(angle) * distance,
  239.             y = crash_position.y + math.sin(angle) * distance
  240.         }
  241.  
  242.         local too_close = false
  243.         for i = 1, #placed_positions do
  244.             local ep = placed_positions[i]
  245.             local dx = fragment_pos.x - ep.x
  246.             local dy = fragment_pos.y - ep.y
  247.             if dx*dx + dy*dy < 2 then
  248.                 too_close = true
  249.                 break
  250.             end
  251.         end
  252.  
  253.         if not too_close then
  254.             local debris_name = select_random_debris()
  255.  
  256.             if surface.can_place_entity{name = debris_name, position = fragment_pos} then
  257.                 local entity = surface.create_entity{
  258.                     name = debris_name,
  259.                     position = fragment_pos,
  260.                     force = "neutral"
  261.                 }
  262.  
  263.                 if entity and entity.valid then
  264.                     table.insert(placed_positions, fragment_pos)
  265.                     fragments_placed = fragments_placed + 1
  266.                 end
  267.             end
  268.         end
  269.     end
  270.  
  271.     return fragments_placed
  272. end
  273.  
  274. local function execute_debris_crash()
  275.     local available_surfaces = {}
  276.     for i = 1, #DEBRIS_CONFIG.surfaces do
  277.         local surface_name = DEBRIS_CONFIG.surfaces[i]
  278.         if game.surfaces[surface_name] then
  279.             table.insert(available_surfaces, surface_name)
  280.         end
  281.     end
  282.  
  283.     -- FIX: storage.rng.debris(i) instead of math.random(i) for the shuffle
  284.     for i = #available_surfaces, 2, -1 do
  285.         local j = storage.rng.debris(i)
  286.         available_surfaces[i], available_surfaces[j] = available_surfaces[j], available_surfaces[i]
  287.     end
  288.  
  289.     for i = 1, #available_surfaces do
  290.         local surface_name = available_surfaces[i]
  291.         local surface = game.surfaces[surface_name]
  292.         local debris_count = count_debris_on_surface(surface)
  293.  
  294.         if debris_count >= DEBRIS_CONFIG.max_debris then
  295.             storage.events.debris.enabled = false
  296.             game.print(
  297.                 "[color=orange][Debris System][/color] Maximum debris limit reached (" ..
  298.                 debris_count .. "/" .. DEBRIS_CONFIG.max_debris ..
  299.                 "). System paused until debris is cleared."
  300.             )
  301.             return false
  302.         end
  303.  
  304.         local crash_position = find_valid_spawn_location(surface)
  305.  
  306.         if crash_position then
  307.             create_crash_effects(surface, crash_position)
  308.  
  309.             -- FIX: storage.rng.debris(min, max) instead of math.random(min, max)
  310.             local fragment_count = storage.rng.debris(DEBRIS_CONFIG.min_fragments, DEBRIS_CONFIG.max_fragments)
  311.             local placed_count = scatter_fragments(surface, crash_position, fragment_count)
  312.  
  313.             game.print(
  314.                 "[color=yellow][Nauvis][/color] " ..
  315.                 placed_count .. " fragments crashed on " .. surface_name ..
  316.                 " [gps=" .. math.floor(crash_position.x) .. "," ..
  317.                 math.floor(crash_position.y) .. "," .. surface_name .. "] (" ..
  318.                 (debris_count + placed_count) .. "/" .. DEBRIS_CONFIG.max_debris .. " total)"
  319.             )
  320.  
  321.             game.forces["player"].add_chart_tag(surface, {
  322.                 position = crash_position,
  323.                 text = "Debris [entity=debris-a] "
  324.             })
  325.  
  326.             return true
  327.         end
  328.     end
  329.  
  330.     return false
  331. end
  332.  
  333. -- ============================================================================
  334. -- POLLUTION MONITORING SYSTEM (unchanged logic, only rng calls replaced)
  335. -- ============================================================================
  336.  
  337. local function get_pollution_level(pollution_value)
  338.     if pollution_value < POLLUTION_CONFIG.thresholds.low      then return "clean"
  339.     elseif pollution_value < POLLUTION_CONFIG.thresholds.medium   then return "low"
  340.     elseif pollution_value < POLLUTION_CONFIG.thresholds.high     then return "medium"
  341.     elseif pollution_value < POLLUTION_CONFIG.thresholds.critical then return "high"
  342.     else return "critical"
  343.     end
  344. end
  345.  
  346. local function check_pollution_on_surface(surface)
  347.     if not surface or not surface.valid then return 0 end
  348.  
  349.     local total_pollution = 0
  350.     for chunk in surface.get_chunks() do
  351.         local chunk_center = {x = chunk.x * 32 + 16, y = chunk.y * 32 + 16}
  352.         total_pollution = total_pollution + surface.get_pollution(chunk_center)
  353.     end
  354.  
  355.     return total_pollution
  356. end
  357.  
  358. local function find_nearest_vent(surface, origin)
  359.     local area = {
  360.         {origin.x - POLLUTION_CONFIG.vent_search_radius, origin.y - POLLUTION_CONFIG.vent_search_radius},
  361.         {origin.x + POLLUTION_CONFIG.vent_search_radius, origin.y + POLLUTION_CONFIG.vent_search_radius}
  362.     }
  363.  
  364.     local vents = surface.find_entities_filtered{area = area, name = "eneas-vent"}
  365.     if #vents == 0 then return nil end
  366.  
  367.     local closest_vent, closest_distance = nil, math.huge
  368.     for i = 1, #vents do
  369.         local vent = vents[i]
  370.         local dx = vent.position.x - origin.x
  371.         local dy = vent.position.y - origin.y
  372.         local distance = math.sqrt(dx*dx + dy*dy)
  373.         if distance < closest_distance then
  374.             closest_distance = distance
  375.             closest_vent = vent
  376.         end
  377.     end
  378.  
  379.     return closest_vent
  380. end
  381.  
  382. local function execute_gas_venting(surface)
  383.     local origin = {x = 0, y = 0}
  384.     local vent = find_nearest_vent(surface, origin)
  385.  
  386.     -- FIX: storage.rng.pollution(min, max) instead of math.random(min, max)
  387.     local pollution_amount = storage.rng.pollution(
  388.         POLLUTION_CONFIG.vent_pollution_min,
  389.         POLLUTION_CONFIG.vent_pollution_max
  390.     )
  391.  
  392.     if not vent then
  393.         surface.pollute(origin, pollution_amount)
  394.         game.print("[color=red][Eneas][/color] Eneas core venting: " ..
  395.             pollution_amount .. " pollution released from Eneas depths.")
  396.         return true
  397.     end
  398.  
  399.     surface.pollute(vent.position, pollution_amount)
  400.     surface.create_entity{name = "poison-cloud", position = vent.position}
  401.     surface.play_sound{path = "utility/axe_mining_ore", position = vent.position, volume_modifier = 0.8}
  402.  
  403.     game.print(
  404.         "[color=red][Eneas][/color] Eneas venting cycle: " ..
  405.         pollution_amount .. " pollution at " ..
  406.         "[gps=" .. math.floor(vent.position.x) .. "," ..
  407.         math.floor(vent.position.y) .. ",eneas]"
  408.     )
  409.  
  410.     game.forces["player"].add_chart_tag(surface, {
  411.         position = vent.position,
  412.         text = "Vent [item=poison-capsule]"
  413.     })
  414.  
  415.     return true
  416. end
  417.  
  418. -- ============================================================================
  419. -- SUPPLY SYSTEM (unchanged logic, only rng calls replaced)
  420. -- ============================================================================
  421.  
  422. local function calculate_yield_multiplier(force)
  423.     local total_multiplier = 1.0
  424.     for tech_name, multiplier in pairs(YIELD_BOOST_TECHS) do
  425.         if force.technologies[tech_name] and force.technologies[tech_name].researched then
  426.             total_multiplier = total_multiplier * multiplier
  427.         end
  428.     end
  429.     return total_multiplier
  430. end
  431.  
  432. local function build_reward_table(force)
  433.     local rewards = {}
  434.  
  435.     for item, amounts in pairs(SUPPLY_REWARDS.base) do
  436.         rewards[item] = amounts
  437.     end
  438.  
  439.     if force.technologies[SUPPLY_REWARDS.early_memory.tech_required] and
  440.        force.technologies[SUPPLY_REWARDS.early_memory.tech_required].researched then
  441.         for item, amounts in pairs(SUPPLY_REWARDS.early_memory.items) do
  442.             rewards[item] = amounts
  443.         end
  444.     end
  445.  
  446.     if force.technologies[SUPPLY_REWARDS.mid_memory.tech_required] and
  447.        force.technologies[SUPPLY_REWARDS.mid_memory.tech_required].researched then
  448.         for item, amounts in pairs(SUPPLY_REWARDS.mid_memory.items) do
  449.             rewards[item] = amounts
  450.         end
  451.     end
  452.  
  453.     if force.technologies[SUPPLY_REWARDS.late_memory.tech_required] and
  454.        force.technologies[SUPPLY_REWARDS.late_memory.tech_required].researched then
  455.         for item, amounts in pairs(SUPPLY_REWARDS.late_memory.items) do
  456.             rewards[item] = amounts
  457.         end
  458.     end
  459.  
  460.     return rewards
  461. end
  462.  
  463. local function create_scaled_rewards(available_items, pollution_multiplier, yield_multiplier)
  464.     local final_rewards = {}
  465.  
  466.     for item, amounts in pairs(available_items) do
  467.         -- FIX: storage.rng.pollution(min, max) instead of math.random(min, max).
  468.         -- We reuse the pollution generator here since rewards are calculated
  469.         -- as part of the pollution check cycle. Using the same generator for
  470.         -- the whole pollution event keeps the logic grouped naturally.
  471.         local base_amount = storage.rng.pollution(amounts.min, amounts.max)
  472.         local scaled_amount = math.floor(base_amount * pollution_multiplier * yield_multiplier)
  473.  
  474.         if scaled_amount > 0 then
  475.             final_rewards[item] = scaled_amount
  476.         end
  477.     end
  478.  
  479.     return final_rewards
  480. end
  481.  
  482. local function deliver_supplies_to_lift(surface, force, rewards, pollution_level)
  483.     -- Find the eneas-lift entity (should be at or near origin)
  484.     local lift = surface.find_entity("eneas-lift", {0, 0})
  485.    
  486.     if not lift then
  487.         -- Search in a small area around origin if not exactly at 0,0
  488.         local lifts = surface.find_entities_filtered{
  489.             name = "eneas-lift",
  490.             position = {0, 0},
  491.             radius = 50
  492.         }
  493.        
  494.         if #lifts > 0 then
  495.             lift = lifts[1]
  496.         end
  497.     end
  498.    
  499.     if not lift or not lift.valid then
  500.         game.print("[color=orange][Unit-05][/color] Supply delivery failed - transport hub offline.")
  501.         return false
  502.     end
  503.    
  504.     -- Get the lift's inventory
  505.     local inventory = lift.get_inventory(defines.inventory.chest)
  506.    
  507.     if not inventory then
  508.         game.print("[color=orange][Unit-05][/color] Supply delivery failed - transport hub malfunction.")
  509.         return false
  510.     end
  511.    
  512.     -- Insert all rewards
  513.     local items_delivered = {}
  514.     for item, count in pairs(rewards) do
  515.         local inserted = inventory.insert{name = item, count = count}
  516.         if inserted > 0 then
  517.             table.insert(items_delivered, inserted .. "x [item=" .. item .. "]")
  518.         end
  519.     end
  520.    
  521.     if #items_delivered > 0 then
  522.         -- Visual and sound feedback at lift location
  523.         surface.create_trivial_smoke{name = "smoke-fast", position = lift.position}
  524.         surface.play_sound{path = "entity-open/steel-chest", position = lift.position}
  525.        
  526.         -- Notification
  527.         local level_color = "green"
  528.         if pollution_level == "low" then level_color = "cyan"
  529.         elseif pollution_level == "medium" then level_color = "yellow"
  530.         elseif pollution_level == "high" then level_color = "orange"
  531.         end
  532.        
  533.         game.print(
  534.             "[color=" .. level_color .. "][Unit-05][/color] Supplies delivered to transport hub: " ..
  535.             table.concat(items_delivered, ", ")
  536.         )
  537.        
  538.         -- Alert all players
  539.         for _, player in pairs(force.connected_players) do
  540.             player.add_custom_alert(lift, {type = "item", name = "eneas-lift"}, "Supply Delivery", true)
  541.             player.play_sound{path = "utility/achievement_unlocked"}
  542.         end
  543.        
  544.         return true
  545.     else
  546.         game.print("[color=orange][Unit-05][/color] Transport hub inventory full - supply delivery delayed.")
  547.         return false
  548.     end
  549. end
  550.  
  551. local function execute_pollution_check()
  552.     for i = 1, #POLLUTION_CONFIG.surfaces do
  553.         local surface_name = POLLUTION_CONFIG.surfaces[i]
  554.         local surface = game.surfaces[surface_name]
  555.        
  556.         if surface then
  557.             -- First check CURRENT pollution levels (before venting)
  558.             local pollution = check_pollution_on_surface(surface)
  559.             local level = get_pollution_level(pollution)
  560.            
  561.             -- Find unit-05 at origin
  562.             local supercomputer = surface.find_entity("unit-05", {0, 0})
  563.            
  564.             -- Handle different pollution levels
  565.             if level == "clean" then
  566.                 game.print("[color=green][Eneas][/color] Air quality is excellent. Pollution: " ..
  567.                     math.floor(pollution) .. " units")
  568.                
  569.                 if supercomputer and supercomputer.valid then
  570.                     supercomputer.active = true
  571.                 end
  572.                
  573.                 -- Deliver supplies at clean air multiplier
  574.                 local force = game.forces["player"]
  575.                 local available_items = build_reward_table(force)
  576.                 local yield_multiplier = calculate_yield_multiplier(force)
  577.                 local rewards = create_scaled_rewards(available_items, POLLUTION_REWARD_MULTIPLIERS.clean, yield_multiplier)
  578.                 deliver_supplies_to_lift(surface, force, rewards, "clean")
  579.                
  580.             elseif level == "low" then
  581.                 game.print("[color=cyan][Eneas][/color] Minor contamination detected. Pollution: " ..
  582.                     math.floor(pollution) .. " units")
  583.                
  584.                 if supercomputer and supercomputer.valid then
  585.                     supercomputer.active = true
  586.                 end
  587.                
  588.                 -- Deliver supplies at low pollution multiplier
  589.                 local force = game.forces["player"]
  590.                 local available_items = build_reward_table(force)
  591.                 local yield_multiplier = calculate_yield_multiplier(force)
  592.                 local rewards = create_scaled_rewards(available_items, POLLUTION_REWARD_MULTIPLIERS.low, yield_multiplier)
  593.                 deliver_supplies_to_lift(surface, force, rewards, "low")
  594.                
  595.             elseif level == "medium" then
  596.                 game.print("[color=yellow][Eneas][/color] Moderate contamination levels detected. Pollution: " ..
  597.                     math.floor(pollution) .. " units")
  598.                
  599.                 if supercomputer and supercomputer.valid then
  600.                     supercomputer.active = true
  601.                 end
  602.                
  603.                 -- Deliver supplies at normal multiplier
  604.                 local force = game.forces["player"]
  605.                 local available_items = build_reward_table(force)
  606.                 local yield_multiplier = calculate_yield_multiplier(force)
  607.                 local rewards = create_scaled_rewards(available_items, POLLUTION_REWARD_MULTIPLIERS.medium, yield_multiplier)
  608.                 deliver_supplies_to_lift(surface, force, rewards, "medium")
  609.                
  610.             elseif level == "high" then
  611.                 game.print("[color=orange][Eneas][/color] Warning: High contamination detected! Pollution: " ..
  612.                     math.floor(pollution) .. " units")
  613.                
  614.                 if supercomputer and supercomputer.valid then
  615.                     supercomputer.active = true
  616.                 end
  617.                
  618.                 -- Deliver reduced supplies
  619.                 local force = game.forces["player"]
  620.                 local available_items = build_reward_table(force)
  621.                 local yield_multiplier = calculate_yield_multiplier(force)
  622.                 local rewards = create_scaled_rewards(available_items, POLLUTION_REWARD_MULTIPLIERS.high, yield_multiplier)
  623.                 deliver_supplies_to_lift(surface, force, rewards, "high")
  624.                
  625.             else -- critical
  626.                 game.print("[color=red][Eneas][/color] CRITICAL: Dangerous pollution levels! Pollution: " ..
  627.                     math.floor(pollution) .. " units")
  628.                
  629.                 -- Disable supercomputer AND low supplies
  630.                 if supercomputer and supercomputer.valid then
  631.                     supercomputer.active = false
  632.                     game.print("[color=red][Unit-05][/color] Argh I cannot think like this! Please help turn down the contamination it is driving me crazy.")
  633.                 end
  634.                 local force = game.forces["player"]
  635.                 local available_items = build_reward_table(force)
  636.                 local yield_multiplier = calculate_yield_multiplier(force)
  637.                 local rewards = create_scaled_rewards(available_items, POLLUTION_REWARD_MULTIPLIERS.high, yield_multiplier)
  638.                 deliver_supplies_to_lift(surface, force, rewards, "high")
  639.            
  640.             end
  641.            
  642.             -- AFTER checking and rewarding, Eneas takes its breath (vents pollution for next cycle)
  643.             execute_gas_venting(surface)
  644.         end
  645.     end
  646.    
  647.     return true
  648. end
  649.  
  650. -- ============================================================================
  651. -- SUPPLY DROP SYSTEM (OLD - kept for compatibility, not used for Eneas)
  652. -- ============================================================================
  653.  
  654. local SUPPLY_DROP_SPAWN_RADIUS = 100
  655.  
  656. local function find_supply_drop_position(surface)
  657.     -- Try to find a valid position within radius of origin
  658.     for attempt = 1, 50 do
  659.         local angle = storage.rng.debris() * 2 * math.pi
  660.         local distance = storage.rng.debris() * SUPPLY_DROP_SPAWN_RADIUS
  661.         local test_position = {
  662.             x = math.cos(angle) * distance,
  663.             y = math.sin(angle) * distance
  664.         }
  665.        
  666.         local drop_position = surface.find_non_colliding_position(
  667.             "steel-chest",
  668.             test_position,
  669.             10,
  670.             1
  671.         )
  672.        
  673.         if drop_position then
  674.             return drop_position
  675.         end
  676.     end
  677.    
  678.     return nil
  679. end
  680.  
  681. function create_supply_drop_for_player(player, target_surface, drop_contents, message)
  682.     if not player.valid then
  683.         return false
  684.     end
  685.    
  686.     local drop_position = find_supply_drop_position(target_surface)
  687.    
  688.     if drop_position then
  689.         local chest = target_surface.create_entity{
  690.             name = "steel-chest",
  691.             position = drop_position,
  692.             force = player.force
  693.         }
  694.        
  695.         if chest and chest.valid then
  696.             chest.backer_name = string.format("Supply Crate for %s", player.name)
  697.            
  698.             for item, count in pairs(drop_contents) do
  699.                 chest.insert{name = item, count = count}
  700.             end
  701.            
  702.             -- Visual and sound feedback
  703.             target_surface.create_trivial_smoke{name = "smoke-fast", position = drop_position}
  704.             target_surface.play_sound{path = "entity-open/steel-chest", position = drop_position}
  705.            
  706.             -- Alert player with custom message or default
  707.             local alert_message = message or "[color=green][Supply Drop][/color] Unit-05 has delivered a supply crate!"
  708.             player.print(alert_message)
  709.             player.add_custom_alert(chest, {type = "item", name = "steel-chest"}, "Supply Drop", true)
  710.             player.play_sound{path = "utility/achievement_unlocked"}
  711.            
  712.             -- Add chart tag
  713.             player.force.add_chart_tag(target_surface, {
  714.                 position = drop_position,
  715.                 text = string.format("%s's Supply", player.name)
  716.             })
  717.            
  718.             return true
  719.         end
  720.     else
  721.         player.print("[color=red]Warning: Could not find safe location for supply drop![/color]")
  722.         return false
  723.     end
  724.    
  725.     return false
  726. end
  727.  
  728. -- ============================================================================
  729. -- MAP TAG CLEANUP SYSTEM
  730. -- ============================================================================
  731.  
  732. local function cleanup_debris_tags()
  733.     for i = 1, #DEBRIS_CONFIG.surfaces do
  734.         local surface_name = DEBRIS_CONFIG.surfaces[i]
  735.         local surface = game.surfaces[surface_name]
  736.        
  737.         if surface then
  738.             local force = game.forces["player"]
  739.             local tags = force.find_chart_tags(surface)
  740.             local removed_count = 0
  741.            
  742.             for _, tag in pairs(tags) do
  743.                 -- Check if this is a debris tag (contains "Debris" in the text)
  744.                 if tag.text and string.find(tag.text, "Debris") then
  745.                     -- Check for debris entities near this tag
  746.                     local area = {
  747.                         {tag.position.x - DEBRIS_CONFIG.tag_check_radius, tag.position.y - DEBRIS_CONFIG.tag_check_radius},
  748.                         {tag.position.x + DEBRIS_CONFIG.tag_check_radius, tag.position.y + DEBRIS_CONFIG.tag_check_radius}
  749.                     }
  750.                    
  751.                     local has_debris = false
  752.                     for j = 1, #DEBRIS_CONFIG.fragments do
  753.                         local debris_name = DEBRIS_CONFIG.fragments[j][1]
  754.                         local debris_nearby = surface.find_entities_filtered{
  755.                             area = area,
  756.                             name = debris_name
  757.                         }
  758.                        
  759.                         if #debris_nearby > 0 then
  760.                             has_debris = true
  761.                             break
  762.                         end
  763.                     end
  764.                    
  765.                     -- If no debris found, remove the tag
  766.                     if not has_debris then
  767.                         tag.destroy()
  768.                         removed_count = removed_count + 1
  769.                     end
  770.                 end
  771.             end
  772.            
  773.             if removed_count > 0 then
  774.                 if DEBUG_MODE then
  775.                     game.print("[color=gray][Debug] Removed " .. removed_count .. " empty debris tags from " .. surface_name .. "[/color]")
  776.                 end
  777.             end
  778.         end
  779.     end
  780.    
  781.     return true
  782. end
  783.  
  784. -- ============================================================================
  785. -- EVENT DISPATCHER
  786. -- ============================================================================
  787.  
  788. local function check_timed_events(event)
  789.     -- Check debris event
  790.     if storage.events.debris.enabled and
  791.        storage.events.debris.next_tick and
  792.        game.tick >= storage.events.debris.next_tick then
  793.        
  794.         local success = execute_debris_crash()
  795.        
  796.         if success then
  797.             storage.events.debris.next_tick = game.tick +
  798.                 storage.rng.debris(DEBRIS_CONFIG.min_interval, DEBRIS_CONFIG.max_interval)
  799.         else
  800.             storage.events.debris.next_tick = game.tick + storage.rng.debris(5 * 60 * 60, 15 * 60 * 60)
  801.         end
  802.     end
  803.    
  804.     -- Check pollution event
  805.     if storage.events.pollution.enabled and
  806.        storage.events.pollution.next_tick and
  807.        game.tick >= storage.events.pollution.next_tick then
  808.        
  809.         execute_pollution_check()
  810.         storage.events.pollution.next_tick = game.tick + POLLUTION_CONFIG.check_interval
  811.     end
  812.    
  813.     -- Check tag cleanup event
  814.     if storage.events.tag_cleanup and
  815.        storage.events.tag_cleanup.enabled and
  816.        storage.events.tag_cleanup.next_tick and
  817.        game.tick >= storage.events.tag_cleanup.next_tick then
  818.        
  819.         cleanup_debris_tags()
  820.         storage.events.tag_cleanup.next_tick = game.tick + DEBRIS_CONFIG.tag_cleanup_interval
  821.     end
  822. end
  823.  
  824. -- ============================================================================
  825. -- DEBRIS MINING HANDLER
  826. -- ============================================================================
  827.  
  828. local function on_debris_mined(event)
  829.     local entity = event.entity
  830.     if not entity or not entity.valid then return end
  831.  
  832.     local player = event.player_index and game.get_player(event.player_index)
  833.  
  834.     local is_debris = false
  835.     for i = 1, #DEBRIS_CONFIG.fragments do
  836.         if entity.name == DEBRIS_CONFIG.fragments[i][1] then
  837.             is_debris = true
  838.             storage.events.debris.enabled = true
  839.             break
  840.         end
  841.     end
  842.  
  843.     if is_debris and player and player.valid and player.character then
  844.         -- FIX: storage.rng.loot() instead of math.random()
  845.         -- We use a dedicated loot generator so the loot rolls are independent
  846.         -- from debris spawn rolls — they won't interfere with each other's
  847.         -- sequences even if one system fires more often than the other.
  848.         local roll = storage.rng.loot()
  849.  
  850.         if roll < DEBRIS_CONFIG.bonus_loot_chance then
  851.             -- FIX: storage.rng.loot(N) instead of math.random(N)
  852.             local loot_entry = DEBRIS_CONFIG.bonus_loot_table[
  853.                 storage.rng.loot(#DEBRIS_CONFIG.bonus_loot_table)
  854.             ]
  855.             local item_name  = loot_entry[1]
  856.             local min_count  = loot_entry[2]
  857.             local max_count  = loot_entry[3]
  858.             -- FIX: storage.rng.loot(min, max) instead of math.random(min, max)
  859.             local count = storage.rng.loot(min_count, max_count)
  860.  
  861.             local inserted = player.insert{name = item_name, count = count}
  862.             if inserted < count then
  863.                 player.surface.spill_item_stack{
  864.                     position = player.position,
  865.                     stack = {name = item_name, count = count - inserted}
  866.                 }
  867.             end
  868.  
  869.             player.print("[color=yellow]You find something valuable among the rubble![/color]")
  870.             player.play_sound{path = "utility/scenario_message"}
  871.         end
  872.     end
  873. end
  874.  
  875. -- ============================================================================
  876. -- EVENT REGISTRATION (unchanged)
  877. -- ============================================================================
  878.  
  879. script.on_nth_tick(60, check_timed_events)
  880. script.on_event(defines.events.on_player_mined_entity, on_debris_mined)
  881. script.on_event(defines.events.on_robot_mined_entity, on_debris_mined)
  882.  
  883. -- ============================================================================
  884. -- COMMANDS
  885. -- ============================================================================
  886.  
  887. commands.add_command("debris-crash", "Manually trigger a debris crash event", function(command)
  888.     local player = command.player_index and game.get_player(command.player_index)
  889.    
  890.     if command.player_index and (not player or not player.admin) then
  891.         player.print("[color=red]Admin privileges required.[/color]")
  892.         return
  893.     end
  894.    
  895.     local success = execute_debris_crash()
  896.     local message = success and "Debris crash triggered." or "Failed to find valid spawn location or limit reached."
  897.    
  898.     if player then player.print(message) else print(message) end
  899. end)
  900.  
  901. commands.add_command("pollution-check", "Manually trigger a pollution check", function(command)
  902.     local player = command.player_index and game.get_player(command.player_index)
  903.    
  904.     if command.player_index and (not player or not player.admin) then
  905.         player.print("[color=red]Admin privileges required.[/color]")
  906.         return
  907.     end
  908.    
  909.     execute_pollution_check()
  910. end)
  911.  
  912. commands.add_command("gas-vent", "Manually trigger a gas venting event", function(command)
  913.     local player = command.player_index and game.get_player(command.player_index)
  914.    
  915.     if command.player_index and (not player or not player.admin) then
  916.         player.print("[color=red]Admin privileges required.[/color]")
  917.         return
  918.     end
  919.    
  920.     local surface = game.surfaces["eneas"]
  921.     if surface then
  922.         execute_gas_venting(surface)
  923.     else
  924.         local msg = "Eneas surface not found."
  925.         if player then player.print(msg) else print(msg) end
  926.     end
  927. end)
  928.  
  929. commands.add_command("debris-schedule", "Set next debris crash (in minutes)", function(command)
  930.     local player = command.player_index and game.get_player(command.player_index)
  931.    
  932.     if command.player_index and (not player or not player.admin) then
  933.         player.print("[color=red]Admin privileges required.[/color]")
  934.         return
  935.     end
  936.    
  937.     local minutes = tonumber(command.parameter)
  938.     if not minutes or minutes < 0 then
  939.         local msg = "Usage: /debris-schedule <minutes>"
  940.         if player then player.print(msg) else print(msg) end
  941.         return
  942.     end
  943.    
  944.     storage.events.debris.next_tick = game.tick + (minutes * 60 * 60)
  945.     local msg = "Next debris crash scheduled in " .. minutes .. " minutes."
  946.     if player then player.print(msg) else print(msg) end
  947. end)
  948.  
  949. commands.add_command("pollution-schedule", "Set next pollution check (in minutes)", function(command)
  950.     local player = command.player_index and game.get_player(command.player_index)
  951.    
  952.     if command.player_index and (not player or not player.admin) then
  953.         player.print("[color=red]Admin privileges required.[/color]")
  954.         return
  955.     end
  956.    
  957.     local minutes = tonumber(command.parameter)
  958.     if not minutes or minutes < 0 then
  959.         local msg = "Usage: /pollution-schedule <minutes>"
  960.         if player then player.print(msg) else print(msg) end
  961.         return
  962.     end
  963.    
  964.     storage.events.pollution.next_tick = game.tick + (minutes * 60 * 60)
  965.     local msg = "Next pollution check scheduled in " .. minutes .. " minutes."
  966.     if player then player.print(msg) else print(msg) end
  967. end)
  968.  
  969. commands.add_command("debris-count", "Check current debris count on all surfaces", function(command)
  970.     local player = command.player_index and game.get_player(command.player_index)
  971.    
  972.     for i = 1, #DEBRIS_CONFIG.surfaces do
  973.         local surface_name = DEBRIS_CONFIG.surfaces[i]
  974.         if game.surfaces[surface_name] then
  975.             local count = count_debris_on_surface(game.surfaces[surface_name])
  976.             local msg = surface_name .. ": " .. count .. "/" .. DEBRIS_CONFIG.max_debris .. " debris"
  977.             if player then player.print(msg) else print(msg) end
  978.         end
  979.     end
  980.    
  981.     local status = storage.events.debris.enabled and "enabled" or "disabled"
  982.     local msg = "Debris system: " .. status
  983.     if player then player.print(msg) else print(msg) end
  984. end)
  985.  
  986. commands.add_command("event-status", "Check status of all timed events", function(command)
  987.     local player = command.player_index and game.get_player(command.player_index)
  988.    
  989.     local function print_msg(msg)
  990.         if player then player.print(msg) else print(msg) end
  991.     end
  992.    
  993.     print_msg("=== Timed Events Status ===")
  994.    
  995.     -- Debris status
  996.     local debris_status = storage.events.debris.enabled and "enabled" or "disabled"
  997.     local debris_time = storage.events.debris.next_tick and
  998.         math.floor((storage.events.debris.next_tick - game.tick) / 60 / 60) or "N/A"
  999.     print_msg("Debris: " .. debris_status .. " | Next in: " .. debris_time .. " minutes")
  1000.  
  1001.     -- tag status
  1002.     local tag_status = storage.events.tag_cleanup.enabled and "enabled" or "disabled"
  1003.     local tag_time = storage.events.tag_cleanup.next_tick and
  1004.         math.floor((storage.events.tag_cleanup.next_tick - game.tick) / 60 / 60) or "N/A"
  1005.     print_msg("Tag cleanup: " .. tag_status .. " | Next in: " .. tag_time .. " minutes")
  1006.    
  1007.     -- Pollution status
  1008.     local pollution_status = storage.events.pollution.enabled and "enabled" or "disabled"
  1009.     local pollution_time = storage.events.pollution.next_tick and
  1010.         math.floor((storage.events.pollution.next_tick - game.tick) / 60 / 60) or "N/A"
  1011.     print_msg("Pollution: " .. pollution_status .. " | Next in: " .. pollution_time .. " minutes")
  1012. end)
Advertisement
Add Comment
Please, Sign In to add comment