762 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			762 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| {% from "macros.lua" import dict_to_lua %}
 | |
| -- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
 | |
| require "lib"
 | |
| require "util"
 | |
| 
 | |
| FREE_SAMPLES = {{ free_samples }}
 | |
| SLOT_NAME = "{{ slot_name }}"
 | |
| SEED_NAME = "{{ seed_name }}"
 | |
| FREE_SAMPLE_BLACKLIST = {{ dict_to_lua(free_sample_blacklist) }}
 | |
| TRAP_EVO_FACTOR = {{ evolution_trap_increase }} / 100
 | |
| MAX_SCIENCE_PACK = {{ max_science_pack }}
 | |
| GOAL = {{ goal }}
 | |
| ARCHIPELAGO_DEATH_LINK_SETTING = "archipelago-death-link-{{ slot_player }}-{{ seed_name }}"
 | |
| ENERGY_INCREMENT = {{ energy_link * 10000000 }}
 | |
| ENERGY_LINK_EFFICIENCY = 0.75
 | |
| 
 | |
| if settings.global[ARCHIPELAGO_DEATH_LINK_SETTING].value then
 | |
|     DEATH_LINK = 1
 | |
| else
 | |
|     DEATH_LINK = 0
 | |
| end
 | |
| 
 | |
| CURRENTLY_DEATH_LOCK = 0
 | |
| 
 | |
| {% if chunk_shuffle %}
 | |
| LAST_POSITIONS = {}
 | |
| GENERATOR = nil
 | |
| NORTH = 1
 | |
| EAST = 2
 | |
| SOUTH = 3
 | |
| WEST = 4
 | |
| ER_COLOR = {1, 1, 1, 0.2}
 | |
| ER_SEED = {{ random.randint(4294967295, 2*4294967295)}}
 | |
| CURRENTLY_MOVING = false
 | |
| ER_FRAMES = {}
 | |
| CHUNK_OFFSET = {
 | |
| [NORTH] = {0, 1},
 | |
| [EAST] = {1, 0},
 | |
| [SOUTH] = {0, -1},
 | |
| [WEST] = {-1, 0}
 | |
| }
 | |
| 
 | |
| 
 | |
| function on_player_changed_position(event)
 | |
|     if CURRENTLY_MOVING == true then
 | |
|         return
 | |
|     end
 | |
|     local player_id = event.player_index
 | |
|     local player = game.get_player(player_id)
 | |
|     local character = player.character -- can be nil, such as spectators
 | |
| 
 | |
|     if character == nil then
 | |
|         return
 | |
|     end
 | |
|     local last_position = LAST_POSITIONS[player_id]
 | |
|     if last_position == nil then
 | |
|         LAST_POSITIONS[player_id] = character.position
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     last_x_chunk = math.floor(last_position.x / 32)
 | |
|     current_x_chunk = math.floor(character.position.x / 32)
 | |
|     last_y_chunk = math.floor(last_position.y / 32)
 | |
|     current_y_chunk = math.floor(character.position.y / 32)
 | |
|     if (ER_FRAMES[player_id] ~= nil and rendering.is_valid(ER_FRAMES[player_id])) then
 | |
|         rendering.destroy(ER_FRAMES[player_id])
 | |
|     end
 | |
|     ER_FRAMES[player_id] = rendering.draw_rectangle{
 | |
|         color=ER_COLOR, width=1, filled=false, left_top = {current_x_chunk*32, current_y_chunk*32},
 | |
|         right_bottom={current_x_chunk*32+32, current_y_chunk*32+32}, players={player}, time_to_live=60,
 | |
|         draw_on_ground= true, only_in_alt_mode = true, surface=character.surface}
 | |
|     if current_x_chunk == last_x_chunk and current_y_chunk == last_y_chunk then -- nothing needs doing
 | |
|         return
 | |
|     end
 | |
|     if ((last_position.x - character.position.x) ^ 2 + (last_position.y - character.position.y) ^ 2) > 4000 then
 | |
|         -- distance too high, death or other teleport took place
 | |
|         LAST_POSITIONS[player_id] = character.position
 | |
|         return
 | |
|     end
 | |
|     -- we'll need a deterministic random state
 | |
|     if GENERATOR == nil or not GENERATOR.valid then
 | |
|         GENERATOR = game.create_random_generator()
 | |
|     end
 | |
| 
 | |
|     -- sufficiently random pattern
 | |
|     GENERATOR.re_seed((ER_SEED + (last_x_chunk * 1730000000) + (last_y_chunk * 97000)) % 4294967295)
 | |
|     -- we now need all 4 exit directions deterministically shuffled to the 4 outgoing directions.
 | |
|     local exit_table = {
 | |
|     [1] = 1,
 | |
|     [2] = 2,
 | |
|     [3] = 3,
 | |
|     [4] = 4
 | |
|     }
 | |
|     exit_table = fisher_yates_shuffle(exit_table)
 | |
|     if current_x_chunk > last_x_chunk then -- going right/east
 | |
|         outbound_direction = EAST
 | |
|     elseif current_x_chunk < last_x_chunk then -- going left/west
 | |
|         outbound_direction = WEST
 | |
|     end
 | |
| 
 | |
|     if current_y_chunk > last_y_chunk then -- going down/south
 | |
|         outbound_direction = SOUTH
 | |
|     elseif current_y_chunk < last_y_chunk then -- going up/north
 | |
|         outbound_direction = NORTH
 | |
|     end
 | |
|     local target_direction = exit_table[outbound_direction]
 | |
| 
 | |
| 	local target_position = {(CHUNK_OFFSET[target_direction][1] + last_x_chunk) * 32 + 16,
 | |
| 							 (CHUNK_OFFSET[target_direction][2] + last_y_chunk) * 32 + 16}
 | |
|     target_position = character.surface.find_non_colliding_position(character.prototype.name,
 | |
|                                                                     target_position, 32, 0.5)
 | |
|     if target_position ~= nil then
 | |
|         rendering.draw_circle{color = ER_COLOR, radius = 1, filled = true,
 | |
|                               target = {character.position.x, character.position.y}, surface = character.surface,
 | |
|                               time_to_live = 300, draw_on_ground = true}
 | |
|         rendering.draw_line{color = ER_COLOR, width = 3, gap_length = 0.5, dash_length = 0.5,
 | |
|                             from = {character.position.x, character.position.y}, to = target_position,
 | |
|                             surface = character.surface,
 | |
|                             time_to_live = 300, draw_on_ground = true}
 | |
|         CURRENTLY_MOVING = true -- prevent recursive event
 | |
|         character.teleport(target_position)
 | |
|         CURRENTLY_MOVING = false
 | |
|     end
 | |
|     LAST_POSITIONS[player_id] = character.position
 | |
| end
 | |
| 
 | |
| function fisher_yates_shuffle(tbl)
 | |
|     for i = #tbl, 2, -1 do
 | |
|         local j = GENERATOR(i)
 | |
|         tbl[i], tbl[j] = tbl[j], tbl[i]
 | |
|     end
 | |
|     return tbl
 | |
| end
 | |
| 
 | |
| script.on_event(defines.events.on_player_changed_position, on_player_changed_position)
 | |
| {% endif %}
 | |
| 
 | |
| function on_check_energy_link(event)
 | |
|     --- assuming 1 MJ increment and 5MJ battery:
 | |
|     --- first 2 MJ request fill, last 2 MJ push energy, middle 1 MJ does nothing
 | |
|     if event.tick % 60 == 30 then
 | |
|         local surface = game.get_surface(1)
 | |
|         local force = "player"
 | |
|         local bridges = surface.find_entities_filtered({name="ap-energy-bridge", force=force})
 | |
|         local bridgecount = table_size(bridges)
 | |
|         global.forcedata[force].energy_bridges = bridgecount
 | |
|         if global.forcedata[force].energy == nil then
 | |
|             global.forcedata[force].energy = 0
 | |
|         end
 | |
|         if global.forcedata[force].energy < ENERGY_INCREMENT * bridgecount * 5 then
 | |
|             for i, bridge in ipairs(bridges) do
 | |
|                 if bridge.energy > ENERGY_INCREMENT*3 then
 | |
|                     global.forcedata[force].energy = global.forcedata[force].energy + (ENERGY_INCREMENT * ENERGY_LINK_EFFICIENCY)
 | |
|                     bridge.energy = bridge.energy - ENERGY_INCREMENT
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|         for i, bridge in ipairs(bridges) do
 | |
|             if global.forcedata[force].energy < ENERGY_INCREMENT then
 | |
|                 break
 | |
|             end
 | |
|             if bridge.energy < ENERGY_INCREMENT*2 and global.forcedata[force].energy > ENERGY_INCREMENT then
 | |
|                 global.forcedata[force].energy = global.forcedata[force].energy - ENERGY_INCREMENT
 | |
|                 bridge.energy = bridge.energy + ENERGY_INCREMENT
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end
 | |
| if (ENERGY_INCREMENT) then
 | |
|     script.on_event(defines.events.on_tick, on_check_energy_link)
 | |
| end
 | |
| 
 | |
| {% if not imported_blueprints -%}
 | |
| function set_permissions()
 | |
|     local group = game.permissions.get_group("Default")
 | |
|     group.set_allows_action(defines.input_action.open_blueprint_library_gui, false)
 | |
|     group.set_allows_action(defines.input_action.import_blueprint, false)
 | |
|     group.set_allows_action(defines.input_action.import_blueprint_string, false)
 | |
|     group.set_allows_action(defines.input_action.import_blueprints_filtered, false)
 | |
| end
 | |
| {%- endif %}
 | |
| 
 | |
| 
 | |
| function check_spawn_silo(force)
 | |
|     if force.players and #force.players > 0 and force.get_entity_count("rocket-silo") < 1 then
 | |
|         local surface = game.get_surface(1)
 | |
|         local spawn_position = force.get_spawn_position(surface)
 | |
|         spawn_entity(surface, force, "rocket-silo", spawn_position.x, spawn_position.y, 80, true, true)
 | |
|     end
 | |
| end
 | |
| 
 | |
| function check_despawn_silo(force)
 | |
|     if not force.players or #force.players < 1 and force.get_entity_count("rocket-silo") > 0 then
 | |
|         local surface = game.get_surface(1)
 | |
|         local spawn_position = force.get_spawn_position(surface)
 | |
|         local x1 = spawn_position.x - 41
 | |
|         local x2 = spawn_position.x + 41
 | |
|         local y1 = spawn_position.y - 41
 | |
|         local y2 = spawn_position.y + 41
 | |
|         local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
 | |
|                                                      name = "rocket-silo",
 | |
|                                                      force = force}
 | |
|         for i,silo in ipairs(silos) do
 | |
|             silo.destructible = true
 | |
|             silo.destroy()
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| 
 | |
| -- Initialize force data, either from it being created or already being part of the game when the mod was added.
 | |
| function on_force_created(event)
 | |
|     local force = event.force
 | |
|     if type(event.force) == "string" then  -- should be of type LuaForce
 | |
|         force = game.forces[force]
 | |
|     end
 | |
|     force.research_queue_enabled = true
 | |
|     local data = {}
 | |
|     data['earned_samples'] = {{ dict_to_lua(starting_items) }}
 | |
|     data["victory"] = 0
 | |
|     data["death_link_tick"] = 0
 | |
|     data["energy"] = 0
 | |
|     data["energy_bridges"] = 0
 | |
|     global.forcedata[event.force] = data
 | |
| {%- if silo == 2 %}
 | |
|     check_spawn_silo(force)
 | |
| {%- endif %}
 | |
| {%- for tech_name in useless_technologies %}
 | |
|     force.technologies.{{ tech_name }}.researched = true
 | |
| {%- endfor %}
 | |
| end
 | |
| script.on_event(defines.events.on_force_created, on_force_created)
 | |
| 
 | |
| -- Destroy force data.  This doesn't appear to be currently possible with the Factorio API, but here for completeness.
 | |
| function on_force_destroyed(event)
 | |
| {%- if silo == 2 %}
 | |
|     check_despawn_silo(event.force)
 | |
| {%- endif %}
 | |
|     global.forcedata[event.force.name] = nil
 | |
| end
 | |
| 
 | |
| function on_runtime_mod_setting_changed(event)
 | |
|     local force
 | |
|     if event.player_index == nil then
 | |
|         force = game.forces.player
 | |
|     else
 | |
|         force = game.players[event.player_index].force
 | |
|     end
 | |
| 
 | |
|     if event.setting == ARCHIPELAGO_DEATH_LINK_SETTING then
 | |
|         if settings.global[ARCHIPELAGO_DEATH_LINK_SETTING].value then
 | |
|             DEATH_LINK = 1
 | |
|         else
 | |
|             DEATH_LINK = 0
 | |
|         end
 | |
|         if force ~= nil then
 | |
|             dumpInfo(force)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| script.on_event(defines.events.on_runtime_mod_setting_changed, on_runtime_mod_setting_changed)
 | |
| 
 | |
| -- Initialize player data, either from them joining the game or them already being part of the game when the mod was
 | |
| -- added.`
 | |
| function on_player_created(event)
 | |
|     local player = game.players[event.player_index]
 | |
|     -- FIXME: This (probably) fires before any other mod has a chance to change the player's force
 | |
|     -- For now, they will (probably) always be on the 'player' force when this event fires.
 | |
|     local data = {}
 | |
|     data['pending_samples'] = table.deepcopy(global.forcedata[player.force.name]['earned_samples'])
 | |
|     global.playerdata[player.index] = data
 | |
|     update_player(player.index)  -- Attempt to send pending free samples, if relevant.
 | |
| {%- if silo == 2 %}
 | |
|     check_spawn_silo(game.players[event.player_index].force)
 | |
| {%- endif %}
 | |
|     dumpInfo(player.force)
 | |
| end
 | |
| script.on_event(defines.events.on_player_created, on_player_created)
 | |
| 
 | |
| -- Create/destroy silo for force if player switched force
 | |
| function on_player_changed_force(event)
 | |
| {%- if silo == 2 %}
 | |
|     check_despawn_silo(event.force)
 | |
|     check_spawn_silo(game.players[event.player_index].force)
 | |
| {%- endif %}
 | |
| end
 | |
| script.on_event(defines.events.on_player_changed_force, on_player_changed_force)
 | |
| 
 | |
| function on_player_removed(event)
 | |
|     global.playerdata[event.player_index] = nil
 | |
| end
 | |
| script.on_event(defines.events.on_player_removed, on_player_removed)
 | |
| 
 | |
| function on_rocket_launched(event)
 | |
|     if event.rocket and event.rocket.valid and global.forcedata[event.rocket.force.name]['victory'] == 0 then
 | |
|         if event.rocket.get_item_count("satellite") > 0 or GOAL == 0 then
 | |
|             global.forcedata[event.rocket.force.name]['victory'] = 1
 | |
|             dumpInfo(event.rocket.force)
 | |
|             game.set_game_state
 | |
|             {
 | |
|                 game_finished = true,
 | |
|                 player_won = true,
 | |
|                 can_continue = true,
 | |
|                 victorious_force = event.rocket.force
 | |
|             }
 | |
|         end
 | |
|     end
 | |
| end
 | |
| script.on_event(defines.events.on_rocket_launched, on_rocket_launched)
 | |
| 
 | |
| -- Updates a player, attempting to send them any pending samples (if relevant)
 | |
| function update_player(index)
 | |
|     local player = game.players[index]
 | |
|     if not player or not player.valid then     -- Do nothing if we reference an invalid player somehow
 | |
|         return
 | |
|     end
 | |
|     local character = player.character or player.cutscene_character
 | |
|     if not character or not character.valid then
 | |
|         return
 | |
|     end
 | |
|     local data = global.playerdata[index]
 | |
|     local samples = data['pending_samples']
 | |
|     local sent
 | |
|     --player.print(serpent.block(data['pending_samples']))
 | |
|     local stack = {}
 | |
| 
 | |
|     for name, count in pairs(samples) do
 | |
|         stack.name = name
 | |
|         stack.count = count
 | |
|         if game.item_prototypes[name] then
 | |
|             if character.can_insert(stack) then
 | |
|                 sent = character.insert(stack)
 | |
|             else
 | |
|                 sent = 0
 | |
|             end
 | |
|             if sent > 0 then
 | |
|                 player.print("Received " .. sent .. "x [item=" .. name .. "]")
 | |
|                 data.suppress_full_inventory_message = false
 | |
|             end
 | |
|             if sent ~= count then               -- Couldn't full send.
 | |
|                 if not data.suppress_full_inventory_message then
 | |
|                     player.print("Additional items will be sent when inventory space is available.", {r=1, g=1, b=0.25})
 | |
|                 end
 | |
|                 data.suppress_full_inventory_message = true -- Avoid spamming them with repeated full inventory messages.
 | |
|                 samples[name] = count - sent    -- Buffer the remaining items
 | |
|                 break                           -- Stop trying to send other things
 | |
|             else
 | |
|                 samples[name] = nil             -- Remove from the list
 | |
|             end
 | |
|         else
 | |
|             player.print("Unable to receive " .. count .. "x [item=" .. name .. "] as this item does not exist.")
 | |
|             samples[name] = nil
 | |
|         end
 | |
|     end
 | |
| 
 | |
| end
 | |
| 
 | |
| -- Update players upon them connecting, since updates while they're offline are suppressed.
 | |
| script.on_event(defines.events.on_player_joined_game, function(event) update_player(event.player_index) end)
 | |
| 
 | |
| function update_player_event(event)
 | |
|     update_player(event.player_index)
 | |
| end
 | |
| 
 | |
| script.on_event(defines.events.on_player_main_inventory_changed, update_player_event)
 | |
| 
 | |
| function add_samples(force, name, count)
 | |
|     local function add_to_table(t)
 | |
|         if count <= 0 then
 | |
|             -- Fixes a bug with single craft, if a recipe gives 0 of a given item.
 | |
|             return
 | |
|         end
 | |
|         t[name] = (t[name] or 0) + count
 | |
|     end
 | |
|     -- Add to global table of earned samples for future new players
 | |
|     add_to_table(global.forcedata[force.name]['earned_samples'])
 | |
|     -- Add to existing players
 | |
|     for _, player in pairs(force.players) do
 | |
|         add_to_table(global.playerdata[player.index]['pending_samples'])
 | |
|         update_player(player.index)
 | |
|     end
 | |
| end
 | |
| 
 | |
| script.on_init(function()
 | |
|     {% if not imported_blueprints %}set_permissions(){% endif %}
 | |
|     global.forcedata = {}
 | |
|     global.playerdata = {}
 | |
|     -- Fire dummy events for all currently existing forces.
 | |
|     local e = {}
 | |
|     for name, _ in pairs(game.forces) do
 | |
|         e.force = name
 | |
|         on_force_created(e)
 | |
|     end
 | |
|     e.force = nil
 | |
| 
 | |
|     -- Fire dummy events for all currently existing players.
 | |
|     for index, _ in pairs(game.players) do
 | |
|         e.player_index = index
 | |
|         on_player_created(e)
 | |
|     end
 | |
| 
 | |
|     if remote.interfaces["silo_script"] then
 | |
|         remote.call("silo_script", "set_no_victory", true)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- hook into researches done
 | |
| script.on_event(defines.events.on_research_finished, function(event)
 | |
|     local technology = event.research
 | |
|     if string.find(technology.force.name, "EE_TESTFORCE") == 1 then
 | |
|         --Don't acknowledge AP research as an Editor Extensions test force
 | |
|         --Also no need for free samples in the Editor extensions testing surfaces, as these testing surfaces
 | |
|         --are worked on exclusively in editor mode.
 | |
|         return
 | |
|     end
 | |
|     if technology.researched and string.find(technology.name, "ap%-") == 1 then
 | |
|         -- check if it came from the server anyway, then we don't need to double send.
 | |
|         dumpInfo(technology.force) --is sendable
 | |
|     else
 | |
|         if FREE_SAMPLES == 0 then
 | |
|             return  -- Nothing else to do
 | |
|         end
 | |
|         if not technology.effects then
 | |
|             return  -- No technology effects, so nothing to do.
 | |
|         end
 | |
|         for _, effect in pairs(technology.effects) do
 | |
|             if effect.type == "unlock-recipe" then
 | |
|                 local recipe = game.recipe_prototypes[effect.recipe]
 | |
|                 for _, result in pairs(recipe.products) do
 | |
|                     if result.type == "item" and result.amount then
 | |
|                         local name = result.name
 | |
|                         if FREE_SAMPLE_BLACKLIST[name] ~= 1 then
 | |
|                             local count
 | |
|                             if FREE_SAMPLES == 1 then
 | |
|                                 count = result.amount
 | |
|                             else
 | |
|                                 count = get_any_stack_size(result.name)
 | |
|                                 if FREE_SAMPLES == 2 then
 | |
|                                     count = math.ceil(count / 2)
 | |
|                                 end
 | |
|                             end
 | |
|                             add_samples(technology.force, name, count)
 | |
|                         end
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| 
 | |
| function dumpInfo(force)
 | |
|     log("Archipelago Bridge Data available for game tick ".. game.tick .. ".") -- notifies client
 | |
| end
 | |
| 
 | |
| 
 | |
| function chain_lookup(table, ...)
 | |
|     for _, k in ipairs{...} do
 | |
|         table = table[k]
 | |
|         if not table then
 | |
|             return nil
 | |
|         end
 | |
|     end
 | |
|     return table
 | |
| end
 | |
| 
 | |
| function kill_players(force)
 | |
|     CURRENTLY_DEATH_LOCK = 1
 | |
|     local current_character = nil
 | |
|     for _, player in ipairs(force.players) do
 | |
|         current_character = player.character
 | |
|         if current_character ~= nil then
 | |
|             current_character.die()
 | |
|         end
 | |
|     end
 | |
|     CURRENTLY_DEATH_LOCK = 0
 | |
| end
 | |
| 
 | |
| function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
 | |
|     local prototype = game.entity_prototypes[name]
 | |
|     local args = {  -- For can_place_entity and place_entity
 | |
|         name = prototype.name,
 | |
|         position = {x = x, y = y},
 | |
|         force = force.name,
 | |
|         build_check_type = defines.build_check_type.blueprint_ghost,
 | |
|         forced = true
 | |
|     }
 | |
| 
 | |
|     local box = prototype.selection_box
 | |
|     local dims = {
 | |
|         w = box.right_bottom.x - box.left_top.x,
 | |
|         h = box.right_bottom.y - box.left_top.y
 | |
|     }
 | |
|     local entity_radius = math.ceil(math.max(dims.w, dims.h) / math.sqrt(2) / 2)
 | |
|     local bounds = {
 | |
|         xmin = math.ceil(x - radius - box.left_top.x),
 | |
|         xmax = math.floor(x + radius - box.right_bottom.x),
 | |
|         ymin = math.ceil(y - radius - box.left_top.y),
 | |
|         ymax = math.floor(y + radius - box.right_bottom.y)
 | |
|     }
 | |
| 
 | |
|     local new_entity = nil
 | |
|     local attempts = 1000
 | |
|     for i = 1,attempts do  -- Try multiple times
 | |
|         -- Find a position
 | |
|         if (randomize and i < attempts-3) or (not randomize and i ~= 1) then
 | |
|             args.position.x = math.random(bounds.xmin, bounds.xmax)
 | |
|             args.position.y = math.random(bounds.ymin, bounds.ymax)
 | |
|         elseif randomize then
 | |
|             args.position.x = x + (i + 3 - attempts) * dims.w
 | |
|             args.position.y = y + (i + 3 - attempts) * dims.h
 | |
|         end
 | |
|         -- Generate required chunks
 | |
|         local x1 = args.position.x + box.left_top.x
 | |
|         local x2 = args.position.x + box.right_bottom.x
 | |
|         local y1 = args.position.y + box.left_top.y
 | |
|         local y2 = args.position.y + box.right_bottom.y
 | |
|         if not surface.is_chunk_generated({x = x1, y = y1}) or
 | |
|            not surface.is_chunk_generated({x = x2, y = y1}) or
 | |
|            not surface.is_chunk_generated({x = x1, y = y2}) or
 | |
|            not surface.is_chunk_generated({x = x2, y = y2}) then
 | |
|             surface.request_to_generate_chunks(args.position, entity_radius)
 | |
|             surface.force_generate_chunk_requests()
 | |
|         end
 | |
|         -- Try to place entity
 | |
|         if surface.can_place_entity(args) then
 | |
|             -- Can hypothetically place this entity here.  Destroy everything underneath it.
 | |
|             local collision_area = {
 | |
|                 {
 | |
|                     args.position.x + prototype.collision_box.left_top.x,
 | |
|                     args.position.y + prototype.collision_box.left_top.y
 | |
|                 },
 | |
|                 {
 | |
|                     args.position.x + prototype.collision_box.right_bottom.x,
 | |
|                     args.position.y + prototype.collision_box.right_bottom.y
 | |
|                 }
 | |
|             }
 | |
|             local entities = surface.find_entities_filtered {
 | |
|                 area = collision_area,
 | |
|                 collision_mask = prototype.collision_mask
 | |
|             }
 | |
|             local can_place = true
 | |
|             for _, entity in pairs(entities) do
 | |
|                 if entity.force and entity.force.name ~= 'neutral' then
 | |
|                     can_place = false
 | |
|                     break
 | |
|                 end
 | |
|             end
 | |
|             local allow_placement_on_resources = not avoid_ores or i > attempts/2
 | |
|             if can_place and not allow_placement_on_resources then
 | |
|                 local resources = surface.find_entities_filtered {
 | |
|                     area = collision_area,
 | |
|                     type = 'resource'
 | |
|                 }
 | |
|                 can_place = (next(resources) == nil)
 | |
|             end
 | |
|             if can_place then
 | |
|                 for _, entity in pairs(entities) do
 | |
|                     entity.destroy({do_cliff_correction=true, raise_destroy=true})
 | |
|                 end
 | |
|                 args.build_check_type = defines.build_check_type.script
 | |
|                 args.create_build_effect_smoke = false
 | |
|                 new_entity = surface.create_entity(args)
 | |
|                 if new_entity then
 | |
|                     new_entity.destructible = false
 | |
|                     new_entity.minable = false
 | |
|                     new_entity.rotatable = false
 | |
|                     break
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|     end
 | |
|     if new_entity == nil then
 | |
|         force.print("Failed to place " .. args.name .. " in " .. serpent.line({x = x, y = y, radius = radius}))
 | |
|     end
 | |
| end
 | |
| 
 | |
| 
 | |
| script.on_event(defines.events.on_entity_died, function(event)
 | |
|     if DEATH_LINK == 0 then
 | |
|         return
 | |
|     end
 | |
|     if CURRENTLY_DEATH_LOCK == 1 then -- don't re-trigger on same event
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local force = event.entity.force
 | |
|     global.forcedata[force.name].death_link_tick = game.tick
 | |
|     dumpInfo(force)
 | |
|     kill_players(force)
 | |
| end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}})
 | |
| 
 | |
| 
 | |
| -- add / commands
 | |
| commands.add_command("ap-sync", "Used by the Archipelago client to get progress information", function(call)
 | |
|     local force
 | |
|     if call.player_index == nil then
 | |
|         force = game.forces.player
 | |
|     else
 | |
|         force = game.players[call.player_index].force
 | |
|     end
 | |
|     local research_done = {}
 | |
|     local forcedata = chain_lookup(global, "forcedata", force.name)
 | |
|     local data_collection = {
 | |
|         ["research_done"] = research_done,
 | |
|         ["victory"] = chain_lookup(forcedata, "victory"),
 | |
|         ["death_link_tick"] = chain_lookup(forcedata, "death_link_tick"),
 | |
|         ["death_link"] = DEATH_LINK,
 | |
|         ["energy"] = chain_lookup(forcedata, "energy"),
 | |
|         ["energy_bridges"] = chain_lookup(forcedata, "energy_bridges"),
 | |
|         ["multiplayer"] = #game.players > 1,
 | |
|     }
 | |
| 
 | |
|     for tech_name, tech in pairs(force.technologies) do
 | |
|         if tech.researched and string.find(tech_name, "ap%-") == 1 then
 | |
|             research_done[tech_name] = tech.researched
 | |
|         end
 | |
|     end
 | |
|     rcon.print(game.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["info"] = data_collection}))
 | |
| end)
 | |
| 
 | |
| commands.add_command("ap-print", "Used by the Archipelago client to print messages", function (call)
 | |
|     game.print(call.parameter)
 | |
| end)
 | |
| 
 | |
| TRAP_TABLE = {
 | |
| ["Attack Trap"] = function ()
 | |
|     game.surfaces["nauvis"].build_enemy_base(game.forces["player"].get_spawn_position(game.get_surface(1)), 25)
 | |
| end,
 | |
| ["Evolution Trap"] = function ()
 | |
|     game.forces["enemy"].evolution_factor = game.forces["enemy"].evolution_factor + (TRAP_EVO_FACTOR * (1 - game.forces["enemy"].evolution_factor))
 | |
|     game.print({"", "New evolution factor:", game.forces["enemy"].evolution_factor})
 | |
| end,
 | |
| ["Teleport Trap"] = function ()
 | |
|     for _, player in ipairs(game.forces["player"].players) do
 | |
|         current_character = player.character
 | |
|         if current_character ~= nil then
 | |
|             current_character.teleport(current_character.surface.find_non_colliding_position(
 | |
|                 current_character.prototype.name, random_offset_position(current_character.position, 1024), 0, 1))
 | |
|         end
 | |
|     end
 | |
| end,
 | |
| ["Grenade Trap"] = function ()
 | |
|     fire_entity_at_players("grenade", 0.1)
 | |
| end,
 | |
| ["Cluster Grenade Trap"] = function ()
 | |
|     fire_entity_at_players("cluster-grenade", 0.1)
 | |
| end,
 | |
| ["Artillery Trap"] = function ()
 | |
|     fire_entity_at_players("artillery-projectile", 1)
 | |
| end,
 | |
| ["Atomic Rocket Trap"] = function ()
 | |
|     fire_entity_at_players("atomic-rocket", 0.1)
 | |
| end,
 | |
| }
 | |
| 
 | |
| commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call)
 | |
|     if global.index_sync == nil then
 | |
|         global.index_sync = {}
 | |
|     end
 | |
|     local tech
 | |
|     local force = game.forces["player"]
 | |
|     if call.parameter == nil then
 | |
|         game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
 | |
|         return
 | |
|     end
 | |
|     chunks = split(call.parameter, "\t")
 | |
|     local item_name = chunks[1]
 | |
|     local index = chunks[2]
 | |
|     local source = chunks[3] or "Archipelago"
 | |
|     if index == nil then
 | |
|         game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
 | |
|         return
 | |
|     elseif index == -1 then -- for coop sync and restoring from an older savegame
 | |
|         tech = force.technologies[item_name]
 | |
|         if tech.researched ~= true then
 | |
|             game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."})
 | |
|             game.play_sound({path="utility/research_completed"})
 | |
|             tech.researched = true
 | |
|         end
 | |
|         return
 | |
|     elseif progressive_technologies[item_name] ~= nil then
 | |
|         if global.index_sync[index] ~= item_name then -- not yet received prog item
 | |
|             global.index_sync[index] = item_name
 | |
|             local tech_stack = progressive_technologies[item_name]
 | |
|             for _, item_name in ipairs(tech_stack) do
 | |
|                 tech = force.technologies[item_name]
 | |
|                 if tech.researched ~= true then
 | |
|                     game.print({"", "Received [technology=" .. tech.name .. "] from ", source})
 | |
|                     game.play_sound({path="utility/research_completed"})
 | |
|                     tech.researched = true
 | |
|                     return
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|     elseif force.technologies[item_name] ~= nil then
 | |
|         tech = force.technologies[item_name]
 | |
|         if tech ~= nil then
 | |
|             global.index_sync[index] = tech
 | |
|             if tech.researched ~= true then
 | |
|                 game.print({"", "Received [technology=" .. tech.name .. "] from ", source})
 | |
|                 game.play_sound({path="utility/research_completed"})
 | |
|                 tech.researched = true
 | |
|             end
 | |
|         end
 | |
|     elseif TRAP_TABLE[item_name] ~= nil then
 | |
|         if global.index_sync[index] ~= item_name then -- not yet received trap
 | |
|             global.index_sync[index] = item_name
 | |
|             game.print({"", "Received ", item_name, " from ", source})
 | |
|             TRAP_TABLE[item_name]()
 | |
|         end
 | |
|     else
 | |
|         game.print("Unknown Item " .. item_name)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| 
 | |
| commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call)
 | |
|     rcon.print(game.table_to_json({
 | |
|         ["slot_name"] = SLOT_NAME,
 | |
|         ["seed_name"] = SEED_NAME,
 | |
|         ["death_link"] = DEATH_LINK,
 | |
|         ["energy_link"] = ENERGY_INCREMENT
 | |
|     }))
 | |
| end)
 | |
| 
 | |
| 
 | |
| {% if allow_cheats -%}
 | |
| commands.add_command("ap-spawn-silo", "Attempts to spawn a silo around 0,0", function(call)
 | |
|     spawn_entity(game.player.surface, game.player.force, "rocket-silo", 0, 0, 80, true, true)
 | |
| end)
 | |
| {% endif -%}
 | |
| 
 | |
| 
 | |
| commands.add_command("ap-deathlink", "Kill all players", function(call)
 | |
|     local force = game.forces["player"]
 | |
|     local source = call.parameter or "Archipelago"
 | |
|     kill_players(force)
 | |
|     game.print("Death was granted by " .. source)
 | |
| end)
 | |
| 
 | |
| commands.add_command("ap-energylink", "Used by the Archipelago client to manage Energy Link", function(call)
 | |
|     local change = tonumber(call.parameter or "0")
 | |
|     local force = "player"
 | |
|     global.forcedata[force].energy = global.forcedata[force].energy + change
 | |
| end)
 | |
| 
 | |
| commands.add_command("energy-link", "Print the status of the Archipelago energy link.", function(call)
 | |
|     log("Player command energy-link") -- notifies client
 | |
| end)
 | |
| 
 | |
| commands.add_command("toggle-ap-send-filter", "Toggle filtering of item sends that get displayed in-game to only those that involve you.", function(call)
 | |
|     log("Player command toggle-ap-send-filter") -- notifies client
 | |
| end)
 | |
| 
 | |
| commands.add_command("toggle-ap-chat", "Toggle sending of chat messages from players on the Factorio server to Archipelago.", function(call)
 | |
|     log("Player command toggle-ap-chat") -- notifies client
 | |
| end)
 | |
| 
 | |
| -- data
 | |
| progressive_technologies = {{ dict_to_lua(progressive_technology_table) }}
 | 
