diff --git a/mtc/control.lua b/mtc/control.lua index 49617b2..eb50be1 100644 --- a/mtc/control.lua +++ b/mtc/control.lua @@ -1,4 +1,5 @@ ---require("scripts.controller") ---require("scripts.main") +require("scripts.global") +require("scripts.controller") +require("scripts.main") diff --git a/mtc/scripts/controller.lua b/mtc/scripts/controller.lua index 7a88e1b..af86025 100644 --- a/mtc/scripts/controller.lua +++ b/mtc/scripts/controller.lua @@ -22,36 +22,6 @@ local function icpairs(a, start_i) end end ---[[ -station: { - deliveries_total: int - train_limit: int - priority: int - last_delivery_tick: int - r_threshold: int >= 0 - p_threshold: int >= 0 - entity: FactorioStop - train_layout: [ [ { - [car_type]: true|nil - } ] ] - accepted_layouts: { - [layout_id]: true|nil - } -} -train: { - layout_id: int - depot_id: int - depot_name: string - item_slot_capacity: int - fluid_capacity: int -} -available_trains: [{ - layout_id: int - capacity: int - all: [train] -}] -]] - local function create_loading_order(stop, manifest) local condition = {} for _, item in ipairs(manifest) do @@ -111,7 +81,8 @@ local function get_valid_train(stations, r_station_id, p_station_id, available_t local valid_train_exists = false local is_fluid = item_type == "fluid" - for k, train in pairs(available_trains.all) do + for train_id, _ in pairs(available_trains) do + local train = map_data.trains[train_id] --check cargo capabilities --check layout validity for both stations if @@ -139,7 +110,7 @@ local function get_valid_train(stations, r_station_id, p_station_id, available_t end end -local function send_train_between(stations, r_station_id, p_station_id, train, primary_item_name, economy) +local function send_train_between(stations, r_station_id, p_station_id, train, available_trains, primary_item_name, economy) local r_station = stations[r_station_id] local p_station = stations[p_station_id] @@ -152,7 +123,7 @@ local function send_train_between(stations, r_station_id, p_station_id, train, p local item_count = v.count local item_type = v.signal.type if item_name and item_type and item_type ~= "virtual" then - local effective_item_count = item_count + r_station.delivery_amount[item_name] + local effective_item_count = item_count + r_station.deliveries[item_name] if -effective_item_count >= r_station.r_threshold then requests[item_name] = -effective_item_count end @@ -165,7 +136,7 @@ local function send_train_between(stations, r_station_id, p_station_id, train, p local item_count = v.count local item_type = v.signal.type if item_name and item_type and item_type ~= "virtual" then - local effective_item_count = item_count + p_station.delivery_amount[item_name] + local effective_item_count = item_count + p_station.deliveries[item_name] if effective_item_count >= p_station.p_threshold then local r = requests[item_name] if r then @@ -222,8 +193,8 @@ local function send_train_between(stations, r_station_id, p_station_id, train, p for _, item in ipairs(manifest) do assert(item.count > 0, "main.lua error, transfer amount was not positive") - r_station.delivery_amount[item.name] = r_station.delivery_amount[item.name] + item.count - p_station.delivery_amount[item.name] = p_station.delivery_amount[item.name] - item.count + r_station.deliveries[item.name] = r_station.deliveries[item.name] + item.count + p_station.deliveries[item.name] = p_station.deliveries[item.name] - item.count local r_stations = economy.r_stations_all[item.name] local p_stations = economy.p_stations_all[item.name] @@ -241,6 +212,12 @@ local function send_train_between(stations, r_station_id, p_station_id, train, p end end + available_trains[train.entity.id] = nil + train.status = STATUS_D_TO_P + train.p_station_id = p_station_id + train.r_station_id = r_station_id + train.manifest = manifest + do local records = {} records[#records + 1] = create_inactivity_order(train.depot_name) @@ -297,7 +274,7 @@ function tick(stations, available_trains, ticks_total) for k, v in pairs(signals) do local item_name = v.signal.name local item_count = v.count - local effective_item_count = item_count + station.delivery_amount[item_name] + local effective_item_count = item_count + station.deliveries[item_name] if -effective_item_count >= station.r_threshold then if r_stations_all[item_name] == nil then @@ -353,7 +330,7 @@ function tick(stations, available_trains, ticks_total) end end if best > 0 then - send_train_between(stations, r_station_id, p_stations[best], best_train, item_name, economy) + send_train_between(stations, r_station_id, p_stations[best], best_train, available_trains, item_name, economy) elseif could_have_been_serviced then failed_because_missing_trains_total = failed_because_missing_trains_total + 1 end @@ -385,7 +362,7 @@ function tick(stations, available_trains, ticks_total) end end if best > 0 then - send_train_between(stations, r_stations[best], p_station_id, best_train, item_name, economy) + send_train_between(stations, r_stations[best], p_station_id, best_train, available_trains, item_name, economy) elseif could_have_been_serviced then failed_because_missing_trains_total = failed_because_missing_trains_total + 1 end diff --git a/mtc/scripts/global.lua b/mtc/scripts/global.lua new file mode 100644 index 0000000..acac881 --- /dev/null +++ b/mtc/scripts/global.lua @@ -0,0 +1,56 @@ +--By Monica Moniot + +--[[ +global: { + total_ticks: int + stations: {[stop_id]: Station} + trains: {[train_id]: Train} + trains_available: {[train_id]: bool} +} +Station: { + deliveries_total: int + train_limit: int + priority: int + last_delivery_tick: int + r_threshold: int >= 0 + p_threshold: int >= 0 + entity: LuaEntity + deliveries: { + [item_name]: int + } + train_layout: [ [ { + [car_type]: bool + } ] ] + accepted_layouts: { + [layout_id]: bool + } +} +Train: { + entity: LuaEntity + layout_id: int + item_slot_capacity: int + fluid_capacity: int + depot_id: int + depot_name: string + status: int + p_station_id: stop_id + r_station_id: stop_id + manifest: [{ + name: string + type: string + count: int + }] +} +]] + +global.total_ticks = 0 +global.stations = {} +global.trains = {} +global.trains_available = {} + +STATUS_D = 0 +STATUS_D_TO_P = 1 +STATUS_P = 2 +STATUS_P_TO_R = 3 +STATUS_R = 4 +STATUS_R_TO_D = 5 diff --git a/mtc/scripts/main.lua b/mtc/scripts/main.lua index 149ce55..7849ab9 100644 --- a/mtc/scripts/main.lua +++ b/mtc/scripts/main.lua @@ -1,163 +1,219 @@ -local function get_item_amount(station, item_id) - return 0 -end - -local function get_valid_train(stations, r_station_i, p_station_i, available_trains) - return {} -end - -local function get_distance(stations, r_station_i, p_station_i) - return 0 -end - -local function send_train_between(stations, r_station_i, p_station_i, train) - local r_station = stations[r_station_i] - local p_station = stations[p_station_i] - r_station.last_p_station_i = p_station_i - p_station.last_r_station_i = r_station_i - - r_station.deliveries_total = r_station.deliveries_total + 1 - p_station.deliveries_total = p_station.deliveries_total + 1 - - local r_amount = get_item_amount(r_station, item_id) + r_station.delivery_amount[item_id] - local p_amount = get_item_amount(p_station, item_id) + p_station.delivery_amount[item_id] - local transfer_amount = math.min(train.capacity, -r_amount, p_amount) - assert(transfer_amount > 0, "main.lua error, transfer amount was not positive") - - r_station.delivery_amount[item_id] = r_station.delivery_amount[item_id] + transfer_amount - p_station.delivery_amount[item_id] = p_station.delivery_amount[item_id] - transfer_amount -end - ---[[ - station: { - deliveries_total: int - train_limit: int - requester_limit: int > 0 - provider_limit: int > 0 - priority: int - last_delivery_tick: int - train_layout: [ [ { - [car_type]: true|nil - } ] ] - accepted_layouts: { - [layout_id]: true|nil +--By Monica Moniot +local function on_station_built(map_data, stop) + local station = { + deliveries_total = 0, + train_limit = 100, + priority = 0, + last_delivery_tick = 0, + r_threshold = 0, + p_threshold = 0, + entity = stop, + --train_layout: [ [ { + -- [car_type]: true|nil + --} ] ] + accepted_layouts = { + --[layout_id]: true|nil } } - available_trains: [{ - layout_id: int - capacity: int - }] -]] + map_data.stations[stop.unit_number] = station +end +local function on_station_broken(map_data, stop) +end -local function icpairs(a, start_i) - start_i = start_i%#a + 1 - local i = start_i - 1 - local flag = true - return function() - i = i%#a + 1 - if i ~= start_i or flag then - flag = false - local v = a[i] - if v then - return i, v +local function find_and_add_all_stations(map_data) + for _, surface in pairs(game.surfaces) do + local stops = surface.find_entities_filtered({type="train-stop"}) + if stops then + for k, stop in pairs(stops) do + if stop.name == BUFFER_STATION_NAME then + local station = map_data.stations[stop.unit_number] + if not station then + on_station_built(map_data, stop) + end + end end end end end - -local function tick(stations, all_items, available_trains, ticks_total) - if #all_items == 0 then - return - end - local failed_because_missing_trains_total = 0 - --psuedo-randomize what item (and what station) to check first so if trains available is low they choose orders psuedo-randomly - for _, item_id in icpairs(all_items, ticks_total) do - local r_stations = {} - local p_stations = {} - - for station_i, station in pairs(stations) do - if station.deliveries_total < station.train_limit then - local item_amount = get_item_amount(station, item_id) + station.delivery_amount[item_id] - - if -item_amount >= station.requester_limit then - table.insert(r_stations, station_i) - elseif item_amount >= station.provider_limit then - table.insert(p_stations, station_i) - end - end +local function on_failed_delivery(map_data, train) + if train.status == STATUS_D or train.status == STATUS_D_TO_P or train.status == STATUS_P then + local station = map_data.stations[train.p_station_id] + for i, item in ipairs(train.manifest) do + station.deliveries[item.name] = station.deliveries[item.name] + item.count end + end + if train.status ~= STATUS_R_TO_D then + local station = map_data.stations[train.r_station_id] + for i, item in ipairs(train.manifest) do + station.deliveries[item.name] = station.deliveries[item.name] - item.count + end + end + --TODO: change circuit outputs + train.r_station_id = 0 + train.p_station_id = 0 + train.manifest = nil + --NOTE: must change train status after call or remove it from tracked trains +end - --we do not dispatch more than one train per station per tick - if #r_stations > 0 and #p_stations > 0 then - if #r_stations <= #p_stations then - --backpressure, prioritize locality - for i, r_station_i in icpairs(r_stations, ticks_total) do - - local best = 0 - local best_train = nil - local best_dist = math.huge - local highest_prior = -math.huge - local could_have_been_serviced = false - for j, p_station_i in ipairs(p_stations) do - local d = get_distance(stations, r_station_i, p_station_i) - local prior = stations[p_station_i].priority - if prior > highest_prior or (prior == highest_prior and d < best_dist) then - local train, is_possible = get_valid_train(stations, r_station_i, p_station_i, available_trains) - if train then - best = j - best_dist = d - best_train = train - highest_prior = prior - elseif is_possible then - could_have_been_serviced = true - end - end - end - if best > 0 then - send_train_between(stations, r_station_i, p_stations[best], best_train) - table.remove(p_stations, best) - elseif could_have_been_serviced then - failed_because_missing_trains_total = failed_because_missing_trains_total + 1 - end - end +local function on_train_arrives_depot(map_data, train_entity) + local train = map_data.trains[train_entity.id] + if train then + if train.manifest then + if train.status == STATUS_R_TO_D then + --succeeded delivery + train.p_station_id = 0 + train.r_station_id = 0 + train.manifest = nil else - --prioritize round robin - for j, p_station_i in icpairs(p_stations, ticks_total) do - - local best = 0 - local best_train = nil - local lowest_tick = math.huge - local highest_prior = -math.huge - local could_have_been_serviced = false - for i, r_station_i in ipairs(r_stations) do - local r_station = stations[r_station_i] - local prior = r_station.priority - if prior > highest_prior or (prior == highest_prior and r_station.last_delivery_tick < lowest_tick) then - local train, is_possible = get_valid_train(stations, r_station_i, p_station_i, available_trains) - if train then - best = i - best_train = train - lowest_tick = r_station.last_delivery_tick - highest_prior = prior - elseif is_possible then - could_have_been_serviced = true - end - end - end - if best > 0 then - send_train_between(stations, r_stations[best], p_station_i, best_train) - table.remove(r_stations, best) - elseif could_have_been_serviced then - failed_because_missing_trains_total = failed_because_missing_trains_total + 1 - end - end + on_failed_delivery(map_data, train) end end + train.depot_id = train_entity.station.unit_number + train.depot_name = train_entity.station.backer_name + train.status = STATUS_D + map_data.trains_available[train_entity.id] = true + else + map_data.trains[train_entity.id] = { + depot_id = train_entity.station.unit_number, + depot_name = train_entity.station.backer_name, + status = STATUS_D, + entity = train_entity, + layout_id = 0, + item_slot_capacity = 0, + fluid_capacity = 0, + p_station = 0, + r_station = 0, + manifest = nil, + } + end + map_data.trains_available[train_entity.id] = true +end + +local function on_train_arrives_buffer(map_data, station_id, train) + if train.manifest then + if train.status == STATUS_D_TO_P then + if train.p_station_id == station_id then + train.status = STATUS_P + --TODO: change circuit outputs + end + elseif train.status == STATUS_P_TO_R then + if train.r_station_id == station_id then + train.status = STATUS_R + --TODO: change circuit outputs + end + else + on_failed_delivery(map_data, train) + map_data.trains[train.entity.id] = nil + end + else + --train is lost somehow, probably from player intervention + map_data.trains[train.entity.id] = nil + end +end + +local function on_train_leaves_buffer(map_data, train) + if train.manifest then + if train.status == STATUS_P then + train.status = STATUS_P_TO_R + local station = map_data.stations[train.p_station_id] + for i, item in ipairs(train.manifest) do + station.deliveries[item.name] = station.deliveries[item.name] + item.count + end + --TODO: change circuit outputs + elseif train.status == STATUS_R then + train.status = STATUS_R_TO_D + local station = map_data.stations[train.r_station_id] + for i, item in ipairs(train.manifest) do + station.deliveries[item.name] = station.deliveries[item.name] - item.count + end + --TODO: change circuit outputs + end end end +local function on_train_broken(map_data, train) + if train.manifest then + on_failed_delivery(map_data, train) + map_data.trains[train.entity.id] = nil + end +end -tick() + +local function on_tick(event) + tick(global.stations, global.trains_available, global.total_ticks) + global.total_ticks = global.total_ticks + 1 +end +local function on_built(event) + local entity = event.entity or event.created_entity or event.destination + if not entity or not entity.valid or entity.name ~= BUFFER_STATION_NAME then return end + + on_station_built(global, entity) +end +local function on_broken(event) + local entity = event.entity + if not entity or not entity.valid then return end + + if entity.train then + local train = global.trains[entity.id] + if train then + on_train_broken(global, entity.train) + end + elseif entity.name == BUFFER_STATION_NAME then + on_station_broken(entity.unit_number) + end +end + +local function on_train_changed(event) + local train_e = event.train + local train = global.trains[train_e.id] + if train_e.state == defines.train_state.wait_station and train_e.station ~= nil then + if train_e.station.name == DEPOT_STATION_NAME then + on_train_arrives_depot(global, train_e) + elseif train_e.station.name == BUFFER_STATION_NAME then + if train then + on_train_arrives_buffer(global, train_e.station.unit_number, train) + end + end + elseif event.old_state == defines.train_state.wait_station then + if train and train.is_at_buffer then + on_train_leaves_buffer(global, train) + end + end +end + +local filter_built = {{filter = "type", type = "train-stop"}} +local filter_broken = {{filter = "type", type = "train-stop"}, {filter = "rolling-stock"}} +local function register_events() + + script.on_event(defines.events.on_built_entity, on_built, filter_built) + script.on_event(defines.events.on_robot_built_entity, on_built, filter_built) + script.on_event({defines.events.script_raised_built, defines.events.script_raised_revive, defines.events.on_entity_cloned}, on_built) + + script.on_event(defines.events.on_pre_player_mined_item, on_broken, filter_broken) + script.on_event(defines.events.on_robot_pre_mined, on_broken, filter_broken) + script.on_event(defines.events.on_entity_died, on_broken, filter_broken) + script.on_event(defines.events.script_raised_destroy, on_broken) + + script.on_event({defines.events.on_pre_surface_deleted, defines.events.on_pre_surface_cleared}, on_surface_removed) + + -- script.on_nth_tick(nil) + script.on_nth_tick(controller_nth_tick, on_tick) + + script.on_event(defines.events.on_train_created, on_train_built) + script.on_event(defines.events.on_train_changed_state, on_train_changed) +end + +script.on_load(function() + register_events() +end) + +script.on_init(function() + register_events() +end) + +script.on_configuration_changed(function(data) + register_events() +end)