From b01d773702930e0a7f4de040dc0385cc447d4a3d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 3 Oct 2022 11:40:03 -0400 Subject: [PATCH] implemented more logic --- .vscode/launch.json | 17 ++- .vscode/settings.json | 8 +- mtc/graphics/place.png | Bin 0 -> 2532 bytes mtc/info.json | 7 +- mtc/scripts/controller.lua | 276 +++++++++++++++++++++++++++++++++++++ 5 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 mtc/graphics/place.png create mode 100644 mtc/scripts/controller.lua diff --git a/.vscode/launch.json b/.vscode/launch.json index 9551917..16592a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,7 @@ "manageMod": true, "adjustMods": { "debugadapter": true, + "flib": true, "mtc": true, }, "disableExtraMods": true @@ -21,13 +22,25 @@ "request": "launch", "name": "Factorio Mod Debug (Settings & Data)", "hookSettings": true, - "hookData": true + "hookData": true, + "adjustMods": { + "debugadapter": true, + "flib": true, + "mtc": true, + }, + "disableExtraMods": true }, { "type": "factoriomod", "request": "launch", "name": "Factorio Mod Debug (Profile)", - "hookMode": "profile" + "hookMode": "profile", + "adjustMods": { + "debugadapter": true, + "flib": true, + "mtc": true, + }, + "disableExtraMods": true } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index c701762..bdf94f1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,6 @@ { - "factorio.package.autoCommitAuthor": "Dessix ", + "factorio.package.autoCommitAuthor": "Mami", "factorio.versions": [ - { - "name": "Steam Factorio", - "factorioPath": "${userHome}/.local/share/Steam/steamapps/common/Factorio/bin/x64/factorio", - "docsPath": "../../../doc-html/runtime-api.json", - "configPath": "${userHome}/.factorio/config/config.ini" - }, { "name": "Steam", "factorioPath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Factorio\\bin\\x64\\factorio.exe", diff --git a/mtc/graphics/place.png b/mtc/graphics/place.png new file mode 100644 index 0000000000000000000000000000000000000000..3f4233236012a0c3d07b99909145f407a013676f GIT binary patch literal 2532 zcmVEX>4Tx04R}tkv&MmKpe$iQ>9ue3U&}t$WUFhAS&W0RV;#q(pG5I!Q|2}Xws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;qmz@OinMK^}g2S3InX6muzVhU}?*F8LZy$kcK_UHZ_JxbPOfJY>rW4d7xZxGLH zS~}-_;vg$X3h_DdxIq^re&o9B@*C&8!vfC?8mYuQagbQdx3Jv8tYE0b)5IY~)hJ&` zyR2~D;;fb`tZ`5N!a!DANphX$5TaN@3~`8%Q9%(USP0Xqkzyi2`;j*OLB}5_mrSlA z7&#VDh6>5?ga5(rZq3}(q?;6o0G%(k{V@Xgc7b};w!e>UyM6+ApMfi_=`YuS*-z4| zO)Yc;^lk$e*G)~{11@)ffhS!uBuDbn6mmJ>{fxdT1N7el{x!F^#y(CTfE0DLd;=UD z0;74#UT^d6uGZfEJ=5s#2b#xnxA)#C(f|Me24YJ`L;(K){{a7>y{D4^000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2j&SK5DP4Bps7><00-bnL_t(|+U;9=Y*W`6|DEG| zedA{yB*ZZx51K-Gc54S?-4hzBDA^{Vc4`@$CecCFYGcyYMtgLTwIVi}YD_|tx`4C| zM(NabUB@F53k5XPg{a!bibz62L*m4C$iqqezSq9r{s<&-Y!W+QLvX%7zw7Uw@Ao_B zdz^ER8E%Tx={i8bP5=^8C|;i*$KG$mnAZnU6hW$rr>iSbTvUiONr(XrMMaj~0Kfsj z;cz@c!1q!O9sn*~xr#p54S2jh47*3*9v#DnA6-HkMIzw)eSLi^CJ0azr60gp%Bc^@ z=)B$&qj&V*z_5EHog|FPvc4Yx*ey+OfB&PL=ov;#OvQ-T?T);6PMzNB_4)JTJr@Ka zwE1)EPkw&=r#n)KoERw)#@<<4Sopsv0#YeEYi(a=L`MLaC$%gBkS|&X6<|MasJHzR z0HwpXR1?5nsI99dVta3e+fDTlW9qgr=Utizy$4{;G_&L2fp0hL+c!FI3D{9vYZ6KS zM*?k~wgxY$2yZ>~#-1(!c*$lnDqi|MV|ZqoYwaovZ$0$-p4NFsz>Zp*RUkS8;E^co zV8@sN-)*qj{L}r|^@{B$0KFEyQZNF}zSgkMb|F;+@Kn51ApA5MJc|(6D+lXs-;4tu z0H~qfwwF};D!|-R^f^#ze=1i1Ptm#>oTiaEQ)eyqg2>we@=Q|?&OZY5c%6WAQzlqP z#NU6RwreCHx{Nm&-@ER_j1*kk4e0Wns z{hn*{y$_g?ga}10-%IdwBH+xK^G^|@?c-UVX%cEbiMYq_uLy@Drip#u4wnoyDrZUjIZ7#*qHw5cg&1T=kcZa0A6q^pIVORGm7uhCl!39##80%0U0YX4$Y9~faLITAgvKYgPqINO3I_YIrnMPbMSieqo+#n+ z%FkjTe+g7JIq&Qne;JO+0r|sDJpZRX$Q>DYAixjjEXJX4y^7&HOJbX$!Mq~8SG^NE z-r5`UEG&pPRkPjR*_zzrM}Yo|B5Dy;o7) zbQD5ZiLv`z&Ca;sZ%Y6GbS_(iupq{~@Z#fJ@ZpB)+p^ugbOp+K+EDC>KG3mSD{$he z@60+|bG5fyE090jk0)9hVhjLkOCeA9Cie09D+fTAc;nBQt_pKok+;} zA+K-3k-3V7a}-PCXSVCEG-KVB=2>k#fgpUL@$akT@j?oC5z-lxOM)}H?>Se(_!{=DH?nhHK=pcjH18#e|?LcZCU z)WE$%?zD zyQ^ai(262NQ4E;PW@kDb9~&DhQWQleD+=OmSr`DVRs`o<$<58RCv$c&AU0wD+_vqF0IFI$u0d8}KZkM7ux7<_Fvc#weeCGk zxrV?+05BU(aUW)zZt7LKnD*=qv;aVkIlgaZlM#$DfY`-3=K#*=004D~WibpoUGxA( zBZ?O11Aq@u52ihNaU2AL-?BI#W}_i`M(_t3J6|I9+bo3oa|;h|x4_jJMUo4jgkGMQ0UUIw!{`<`a;(Xml= 0.6.0" + ] } diff --git a/mtc/scripts/controller.lua b/mtc/scripts/controller.lua new file mode 100644 index 0000000..5de7c1d --- /dev/null +++ b/mtc/scripts/controller.lua @@ -0,0 +1,276 @@ +local math = math +local INF = math.huge + +local function icpairs(a, start_i) + if #a == 0 then + return nil + end + 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 + end + end + end +end + +--[[ +station: { + deliveries_total: int + train_limit: int + priority: int + last_delivery_tick: int + r_threshold: int >= 0 + p_threshold: int >= 0 + train_layout: [ [ { + [car_type]: true|nil + } ] ] + accepted_layouts: { + [layout_id]: true|nil + } +} +train: { + layout_id: int + depot_id: int +} +available_trains: [{ + layout_id: int + capacity: int +}] +]] + + +local function get_signals(stations, station_id) + return {} +end + +local function get_station_dist(stations, id0, id1) + return INF +end + +local function get_valid_train(stations, r_station_id, p_station_id, available_trains) + --NOTE: this code is the critical section for run-time optimization + local r_station = stations[r_station_id] + local p_station = stations[p_station_id] + + local p_to_r_dist = get_station_dist(stations, p_station_id, r_station_id) + if p_to_r_dist == INF then + return nil, p_to_r_dist + end + + local best_train = nil + local best_dist = INF + + for k, train in pairs(available_trains.all) do + --check cargo capabilities + --check layout validity for both stations + --TODO: add check for correct cargo type + if r_station.accepted_layouts[train.layout_id] and p_station.accepted_layouts[train.layout_id] then + --check if exists valid path + --check if path is shortest so we prioritize locality + local d_to_p_dist = get_station_dist(stations, train.depot_id, p_station_id) + + local dist = d_to_p_dist + p_to_r_dist + if dist < best_dist then + best_dist = dist + best_train = train + end + end + end + + return best_train, best_dist +end + +local function send_train_between(stations, r_station_id, p_station_id, train, ticks_total) + local r_station = stations[r_station_id] + local p_station = stations[p_station_id] + + local requests = {} + local orders = {} + local has_liquid = false + local has_solid = false + + local r_signals = get_signals(r_station_id) + for k, v in pairs(r_signals) do + local item_name = v.signal.name + 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] + if -effective_item_count >= r_station.r_threshold then + requests[item_name] = -effective_item_count + end + end + end + + local p_signals = get_signals(r_station_id) + for k, v in pairs(p_signals) do + local item_name = v.signal.name + 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] + if effective_item_count >= p_station.p_threshold then + local r = requests[item_name] + if r then + orders[item_name] = math.min(r, effective_item_count) + if item_type == "liquid" then--TODO: here add liquid detection + has_liquid = true + else + has_solid = true + end + end + end + end + end + + r_station.last_delivery_tick = ticks_total + p_station.last_delivery_tick = ticks_total + + r_station.deliveries_total = r_station.deliveries_total + 1 + p_station.deliveries_total = p_station.deliveries_total + 1 + + for item_name, item_count in pairs(orders) 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 + --set train orders + + end + +end + + +function tick(stations, available_trains, ticks_total) + local r_stations_all = {} + local p_stations_all = {} + local all_items = {} + + for station_id, station in pairs(stations) do + if station.deliveries_total < station.train_limit then + station.r_threshold = 0 + station.p_threshold = 0 + station.priority = 0 + local signals = get_signals(station_id) + for k, v in pairs(signals) do + local item_name = v.signal.name + local item_count = v.count + local item_type = v.signal.type + if item_name and item_type then + if item_type == "virtual" then + if item_name == SIGNAL_PRIORITY then + station.priority = item_count + elseif item_name == REQUEST_THRESHOLD then + station.r_threshold = math.abs(item_count) + elseif item_name == PROVIDE_THRESHOLD then + station.p_threshold = math.abs(item_count) + end + signals[k] = nil + end + else + signals[k] = nil + end + end + 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] + + if -effective_item_count >= station.r_threshold then + if r_stations_all[item_name] == nil then + r_stations_all[item_name] = {} + p_stations_all[item_name] = {} + all_items[#all_items + 1] = item_name + end + table.insert(r_stations_all[item_name], station_id) + elseif effective_item_count >= station.p_threshold then + if r_stations_all[item_name] == nil then + r_stations_all[item_name] = {} + p_stations_all[item_name] = {} + all_items[#all_items + 1] = item_name + end + table.insert(p_stations_all[item_name], station_id) + end + end + end + 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_name in icpairs(all_items, ticks_total) do + --we do not dispatch more than one train per station per tick + local r_stations = r_stations_all[item_name] + local p_stations = p_stations_all[item_name] + + --NOTE: this is an approximation algorithm for solving the assignment problem (bipartite graph weighted matching), the true solution would be to implement the simplex algorithm (and run it twice to compare the locality solution to the round-robin solution) but I strongly believe most factorio players would prefer run-time efficiency over perfect train routing logic + if #r_stations > 0 and #p_stations > 0 then + if #r_stations <= #p_stations then + --probably backpressure, prioritize locality + for i, r_station_id in icpairs(r_stations, ticks_total) do + local best = 0 + local best_train = nil + local best_dist = INF + local highest_prior = -INF + local could_have_been_serviced = false + for j, p_station_id in ipairs(p_stations) do + local train, d = get_valid_train(stations, r_station_id, p_station_id, available_trains) + local prior = stations[p_station_id].priority + if prior > highest_prior or (prior == highest_prior and d < best_dist) then + if train then + best = j + best_dist = d + best_train = train + highest_prior = prior + elseif d < INF then + could_have_been_serviced = true + end + end + end + if best > 0 then + send_train_between(stations, r_station_id, 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 + else + --prioritize round robin + for j, p_station_id in icpairs(p_stations, ticks_total) do + local best = 0 + local best_train = nil + local lowest_tick = INF + local highest_prior = -INF + local could_have_been_serviced = false + for i, r_station_id in ipairs(r_stations) do + local r_station = stations[r_station_id] + local prior = r_station.priority + if prior > highest_prior or (prior == highest_prior and r_station.last_delivery_tick < lowest_tick) then + local train, d = get_valid_train(stations, r_station_id, p_station_id, available_trains) + if train then + best = i + best_train = train + lowest_tick = r_station.last_delivery_tick + highest_prior = prior + elseif d < INF then + could_have_been_serviced = true + end + end + end + if best > 0 then + send_train_between(stations, r_stations[best], p_station_id, 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 + end + end + end +end