babulm

Untitled

Jul 15th, 2026 (edited)
18
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 43.04 KB | None | 0 0
  1. --[[ ================================================================
  2.  
  3.  
  4. Hinweis: rednet.send(id,msg) == modem.transmit(channel=id, replyCh=eigeneID, msg)
  5. broadcast == Kanal 65535 ; GPS == Kanal 65534
  6. ================================================================ ]]--
  7.  
  8. ------------------------------------------------------------------
  9. -- Forward-Deklarationen (ein Upvalue je Modul/Helfer)
  10. ------------------------------------------------------------------
  11. local CONFIG, STATE
  12. local radio, recon, attack, guard, ui, input, scout
  13. local now, logMsg, gameClock, randstr, truncate, cntChannels, safeNum
  14. local eventLoop, attackLoop, guardLoop, uiLoop, scoutLoop, planLoop
  15. local setupWizard, main
  16.  
  17. ------------------------------------------------------------------
  18. -- Konfiguration (zur Laufzeit ueber Tasten anpassbar)
  19. ------------------------------------------------------------------
  20. CONFIG = {
  21. useHttp = true,
  22. timeUrls = {
  23. "http://worldtimeapi.org/api/timezone/Etc/UTC.txt",
  24. "http://worldtimeapi.org/api/ip.txt",
  25. },
  26. tpsWindow = 15, -- Spiel-Sekunden je TPS-Messfenster
  27. tpsFloor = 10, -- unter diesem Wert gilt als Lag
  28. tpsLowDuration = 120, -- Sekunden anhaltend -> drosseln
  29. throttleRate = 3, -- Nachrichten/s waehrend Drossel
  30. baseRate = 25, -- Nachrichten/s Standard-Budget
  31. floodEnabled = true,
  32. observeSecs = 2, -- kurz beobachten ...
  33. spoofSecs = 3, -- ... kurz spoofen ...
  34. fuzzSecs = 6, -- ... dann Fuzzing (Crash-Vektor) + Flood -> schnell zerstoeren
  35. downSilence = 15, -- s Stille nach Angriff -> Absturz-Verdacht
  36. baselineMsgs = 3, -- ab so vielen Nachrichten gilt Knoten als etabliert
  37. attackTick = 0.1,
  38. uiRefresh = 0.3,
  39. planInterval = 1, -- s zwischen Governor-Takt/Kanal-Plan (schnelle Reaktion)
  40. autostart = false, -- Kampagne startet erst per [S]
  41. -- EMPFANG bewusst sparsam + rate-geregelt halten. Ein Modem, das viele
  42. -- Kanaele (v.a. Broadcast 65535 / GPS 65534) offen haelt, empfaengt auf
  43. -- einem vollen Server JEDE Funknachricht -> Event-Flut, die den Server-/
  44. -- Client-Thread einfrieren kann. Deshalb Slow-Start + Rate-Governor:
  45. maxOpenChannels = 48, -- Obergrenze gleichzeitig offener Kanaele
  46. startChannels = 8, -- Slow-Start: so wenige Kanaele zu Beginn
  47. maxRxPerSec = 120, -- Empfangs-Budget; darueber Kanaele/Broadcast abwerfen
  48. panicRxPerSec = 350, -- Notbremse: fast alles schliessen
  49. openGps = false,-- GPS-Kanal 65534 ist eine Dauerflut -> standardmaessig AUS
  50. autoBroadcast = true, -- Broadcast 65535 bei ruhiger Lage DAUERHAFT offen halten (nur so wird viel entdeckt; Notbremse schuetzt vor Flut). false = Broadcast nur per [B]-Taste
  51. bcastCooldown = 15, -- s Abkuehlzeit nach Ueberlast, bevor Broadcast wieder oeffnet (verhindert Flattern)
  52. sweepWindow = 3, -- Kanaele je Sweep-Fenster (klein halten)
  53. lowFill = 8, -- niedrige IDs 0..lowFill-1 als Catch-all
  54. probeMax = 40, -- Kanal-Limit-Probe (klein halten)
  55. rosterCap = 400, -- max. Knoten im Roster (gegen Spoof-ID-Flut)
  56. panicRxBurst = 200, -- Ereignis-Notbremse: so viele Nachr. seit letztem Governor-Reset -> Broadcast SOFORT zu
  57. panicFloor = 6, -- Kanal-Budget nach Notbremse
  58. floodBroadcast = false,-- Flood auch auf Broadcast 65535? (server-weite Fan-out-Last -> Standard AUS)
  59. }
  60.  
  61. ------------------------------------------------------------------
  62. -- Laufzeit-Zustand
  63. ------------------------------------------------------------------
  64. STATE = {
  65. myId = 0,
  66. running = false,
  67. campaignOn = false,
  68. attacksPaused = false,
  69. aggression = "adaptive", -- "stealth" | "adaptive" | "aggressive"
  70. log = {},
  71. roster = {}, -- id -> node
  72. nodeIds = {}, -- array aller bekannten ids
  73. whitelist = {}, -- id -> true (nicht angreifen)
  74. targets = {}, -- id -> attack-state
  75. displays = {},
  76. viewList = {},
  77. selIndex = 1,
  78. rr = 0,
  79. rxCount = 0,
  80. rxWindow = 0,
  81. rxGov = 0, -- Zaehler fuer den Empfangs-Governor
  82. overloaded = false, -- Empfangs-Ueberlast erkannt?
  83. emergencyTripped = false, -- Ereignis-Notbremse bereits ausgeloest?
  84. chOpen = 0, -- aktuell offene Kanaele
  85. sentCount = 0,
  86. sendErr = 0,
  87. nodeCount = 0,
  88. killConfirmed = 0,
  89. maxDist = nil,
  90. }
  91.  
  92. ------------------------------------------------------------------
  93. -- Helfer
  94. ------------------------------------------------------------------
  95. now = function() return os.clock() end -- tick-basierte Spielzeit-Sekunden
  96.  
  97. logMsg = function(s)
  98. local L = STATE.log
  99. L[#L + 1] = "[" .. gameClock() .. "] " .. tostring(s)
  100. while #L > 200 do table.remove(L, 1) end
  101. end
  102.  
  103. gameClock = function()
  104. local ok, t = pcall(os.time) -- 0..24 (Stunden als Float)
  105. if not ok or type(t) ~= "number" then return "--:--" end
  106. local h = math.floor(t)
  107. local m = math.floor((t - h) * 60)
  108. if m > 59 then m = 59 end
  109. return string.format("%02d:%02d", h, m)
  110. end
  111.  
  112. randstr = function(n)
  113. n = n or math.random(3, 10)
  114. local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  115. local t = {}
  116. for i = 1, n do
  117. local k = math.random(#chars)
  118. t[i] = string.sub(chars, k, k)
  119. end
  120. return table.concat(t)
  121. end
  122.  
  123. truncate = function(s, n)
  124. s = tostring(s)
  125. if #s > n then return string.sub(s, 1, n - 1) .. "~" end
  126. return s
  127. end
  128.  
  129. cntChannels = function(nd)
  130. local c = 0
  131. for _ in pairs(nd.channels) do c = c + 1 end
  132. return c
  133. end
  134.  
  135. safeNum = function(v, d)
  136. if type(v) == "number" then return v end
  137. return d
  138. end
  139.  
  140. ------------------------------------------------------------------
  141. -- GUARD : Rate-Limiter (Token-Bucket) + TPS-Messung
  142. ------------------------------------------------------------------
  143. guard = {
  144. rate = CONFIG.baseRate,
  145. baseRate = CONFIG.baseRate,
  146. cap = CONFIG.baseRate,
  147. tokens = CONFIG.baseRate,
  148. last = 0,
  149. mode = "init", -- "http" | "selflimit"
  150. tps = nil,
  151. lowElapsedReal = 0, -- ECHT-Sekunden mit TPS unter Grenze (nur HTTP-Modus)
  152. stableWindows = 0, -- stabile Fenster (Selbst-Limit-Freigabe)
  153. throttled = false,
  154. baseline = nil,
  155. }
  156.  
  157. guard.refill = function()
  158. local t = now()
  159. local dt = t - guard.last
  160. if dt < 0 then dt = 0 end
  161. guard.cap = math.max(2, guard.rate)
  162. guard.tokens = math.min(guard.cap, guard.tokens + dt * guard.rate)
  163. guard.last = t
  164. end
  165.  
  166. -- blockiert bis ein Token frei ist (respektiert Pause)
  167. guard.acquire = function()
  168. while STATE.running do
  169. guard.refill()
  170. if not STATE.attacksPaused and guard.tokens >= 1 then
  171. guard.tokens = guard.tokens - 1
  172. return true
  173. end
  174. local wait
  175. if STATE.attacksPaused then
  176. wait = 0.2
  177. else
  178. local deficit = 1 - guard.tokens
  179. wait = deficit / math.max(guard.rate, 0.01)
  180. if wait < 0.05 then wait = 0.05 end
  181. if wait > 1 then wait = 1 end
  182. end
  183. sleep(wait)
  184. end
  185. return false
  186. end
  187.  
  188. guard.fetchRealTime = function()
  189. if not http then return nil end
  190. for _, url in ipairs(CONFIG.timeUrls) do
  191. local ok, h = pcall(http.get, url)
  192. if ok and h then
  193. local body
  194. pcall(function() body = h.readAll() end)
  195. pcall(function() h.close() end)
  196. if type(body) == "string" then
  197. local n = string.match(body, "unixtime[\"'%s:]*(%d+)")
  198. if not n then n = string.match(body, "(%d%d%d%d%d%d%d%d%d%d+)") end
  199. n = tonumber(n)
  200. if n and n > 1400000000 and n < 4000000000 then return n end
  201. end
  202. end
  203. end
  204. return nil
  205. end
  206.  
  207. guard.detect = function()
  208. if http and CONFIG.useHttp then
  209. local t = guard.fetchRealTime()
  210. if t then
  211. guard.mode = "http"
  212. logMsg("Guard: HTTP TPS-Modus aktiv")
  213. return
  214. end
  215. end
  216. guard.mode = "selflimit"
  217. logMsg("Guard: Selbst-Limit-Modus (kein/kein-nutzbares HTTP)")
  218. end
  219.  
  220. -- realEl = echte Sekunden seit der letzten Messung (aus HTTP-Zeitdifferenz).
  221. -- Wichtig: os.clock ist tick-basiert und laeuft unter Lag langsamer als die
  222. -- Echtzeit; die "120s"-Regel MUSS daher in Echt-Sekunden zaehlen, sonst
  223. -- triggert die Drossel bei starkem Lag viel zu spaet.
  224. guard.applyRule = function(tps, realEl)
  225. if tps < CONFIG.tpsFloor then
  226. guard.lowElapsedReal = guard.lowElapsedReal + math.max(0, realEl or 0)
  227. if guard.lowElapsedReal >= CONFIG.tpsLowDuration and not guard.throttled then
  228. guard.throttled = true
  229. guard.rate = CONFIG.throttleRate
  230. logMsg(string.format("TPS<%.0f seit >%ds (echt) -> DROSSEL (rate=%.0f/s)",
  231. CONFIG.tpsFloor, CONFIG.tpsLowDuration, guard.rate))
  232. end
  233. else
  234. guard.lowElapsedReal = 0
  235. if guard.throttled then
  236. guard.throttled = false
  237. guard.rate = guard.baseRate
  238. logMsg("TPS stabil -> Drossel geloest")
  239. end
  240. end
  241. end
  242.  
  243. -- Fallback ohne HTTP: keine echte TPS. Wir beobachten stattdessen, ob der
  244. -- gesamte Funkverkehr einbricht (indirektes Lag-/Ausfall-Symptom). Freigabe
  245. -- ueber eine Zahl stabiler Fenster statt einer (unter Lag verzerrten) Uhr.
  246. guard.selfHeuristic = function()
  247. local rate = STATE.rxWindow / math.max(CONFIG.tpsWindow, 1)
  248. STATE.rxWindow = 0
  249. if not guard.baseline then
  250. guard.baseline = rate
  251. else
  252. guard.baseline = guard.baseline * 0.7 + rate * 0.3
  253. end
  254. if STATE.campaignOn and not STATE.attacksPaused
  255. and guard.baseline > 1 and rate < guard.baseline * 0.3 then
  256. guard.stableWindows = 0
  257. if not guard.throttled then
  258. guard.throttled = true
  259. guard.rate = CONFIG.throttleRate
  260. logMsg("Selbst-Limit: Funkverkehr eingebrochen -> vorsorglich drosseln")
  261. end
  262. else
  263. guard.stableWindows = guard.stableWindows + 1
  264. if guard.throttled and guard.stableWindows >= 4 then
  265. guard.throttled = false
  266. guard.rate = guard.baseRate
  267. logMsg("Selbst-Limit: Drossel geloest")
  268. end
  269. end
  270. end
  271.  
  272. guard.tick = function()
  273. if guard.mode == "http" then
  274. -- Spieluhr eng um jeden HTTP-Fetch klammern und den Mittelpunkt nehmen,
  275. -- damit Spiel- und Echtzeit dasselbe Intervall messen (sonst verzerrt die
  276. -- Anfrage-Latenz die TPS nach unten).
  277. local g0a = now()
  278. local t0 = guard.fetchRealTime()
  279. local g0b = now()
  280. if not t0 then
  281. guard.mode = "selflimit"
  282. logMsg("Guard: HTTP verloren -> Selbst-Limit")
  283. return
  284. end
  285. local c0 = (g0a + g0b) / 2
  286. sleep(CONFIG.tpsWindow)
  287. local g1a = now()
  288. local t1 = guard.fetchRealTime()
  289. local g1b = now()
  290. if not t1 then
  291. guard.mode = "selflimit"
  292. return
  293. end
  294. local c1 = (g1a + g1b) / 2
  295. local gameEl = c1 - c0
  296. local realEl = t1 - t0
  297. if realEl > 0.5 then
  298. guard.tps = 20 * gameEl / realEl
  299. guard.applyRule(guard.tps, realEl)
  300. end
  301. else
  302. guard.tps = nil
  303. sleep(CONFIG.tpsWindow)
  304. guard.selfHeuristic()
  305. end
  306. end
  307.  
  308. ------------------------------------------------------------------
  309. -- RADIO : Modem-Discovery, Kanal-Management, Senden
  310. ------------------------------------------------------------------
  311. radio = {
  312. side = nil,
  313. dev = nil,
  314. wireless = true, -- ist das gefundene Modem drahtlos? (Wired hoert nichts ueber Funk)
  315. hwLimit = 48, -- vom Modem erlaubtes Maximum (per Probe ermittelt)
  316. budget = CONFIG.startChannels, -- aktuell erlaubte offene Kanaele (Governor)
  317. holdBroadcast = false, -- Broadcast 65535 gerade offen halten?
  318. open = {},
  319. sweepPos = 0,
  320. rxRate = 0, -- gemessene Empfangsrate (Nachr./s)
  321. bcastCooldownUntil = 0, -- bis dahin Broadcast NICHT wieder oeffnen (nach Ueberlast)
  322. }
  323.  
  324. radio.findModem = function()
  325. -- Bevorzugt ein DRAHTLOSES Modem waehlen; ein Wired Modem hoert keinen Funk.
  326. local firstAny = nil
  327. for _, side in ipairs(rs.getSides()) do
  328. if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  329. local wl = true
  330. local dev = peripheral.wrap(side)
  331. pcall(function() if dev.isWireless then wl = dev.isWireless() end end)
  332. if wl then
  333. radio.side = side; radio.dev = dev; radio.wireless = true
  334. return side
  335. elseif not firstAny then
  336. firstAny = { side = side, dev = dev }
  337. end
  338. end
  339. end
  340. if firstAny then -- nur ein Wired Modem gefunden -> nutzen, aber warnen
  341. radio.side = firstAny.side; radio.dev = firstAny.dev; radio.wireless = false
  342. return firstAny.side
  343. end
  344. return nil
  345. end
  346.  
  347. radio.probeLimit = function()
  348. if not radio.dev then return 2 end
  349. pcall(radio.dev.closeAll)
  350. radio.open = {}
  351. local count = 0
  352. for ch = 0, CONFIG.probeMax do
  353. local ok = pcall(radio.dev.open, ch)
  354. if not ok then break end
  355. local isopen = false
  356. pcall(function() isopen = radio.dev.isOpen(ch) end)
  357. pcall(radio.dev.close, ch) -- sofort wieder zu -> nie viele Kanaele gleichzeitig offen
  358. if not isopen then break end
  359. count = count + 1
  360. end
  361. pcall(radio.dev.closeAll)
  362. radio.open = {}
  363. radio.hwLimit = math.max(count, 2)
  364. -- Start bewusst klein (Slow-Start); der Governor waechst nur bei ruhiger Lage.
  365. radio.budget = math.min(radio.hwLimit, CONFIG.startChannels)
  366. return radio.hwLimit
  367. end
  368.  
  369. -- Reihenfolge = Prioritaet: applyPlan kappt die Liste bei radio.budget.
  370. -- Priorisiert werden entdeckte Gegner-IDs (wenig Volumen, hoher Wert).
  371. -- Broadcast (65535) ist die groesste Datenflut und wird NUR kurz/periodisch
  372. -- vom Governor freigegeben; GPS (65534) bleibt standardmaessig ganz aus.
  373. radio.buildDesired = function()
  374. local list = {}
  375. -- 1) entdeckte Knoten-IDs pinnen (gezielter Verkehr an bekannte Gegner) --
  376. -- ABER nur so viele, dass ein Rest des Budgets fuer Broadcast-Stichprobe,
  377. -- Sweep und Low-Fill frei bleibt. Sonst wuerden bei vielen Knoten diese
  378. -- (in der Prioritaet spaeteren) Eintraege komplett verdraengt -> die
  379. -- Broadcast-Erkennung und [B] wuerden still nichts mehr oeffnen.
  380. local reserve = CONFIG.sweepWindow + 2
  381. local nodeCap = math.max(1, radio.budget - reserve)
  382. local cnt = 0
  383. for _, nd in ipairs(recon.recentNodes()) do
  384. if nd.id >= 0 and nd.id < 65534 then
  385. list[#list + 1] = nd.id
  386. cnt = cnt + 1
  387. if cnt >= nodeCap then break end
  388. end
  389. end
  390. -- 2) Broadcast nur wenn der Governor es gerade erlaubt (Stichprobe)
  391. if radio.holdBroadcast then list[#list + 1] = 65535 end
  392. if CONFIG.openGps then list[#list + 1] = 65534 end
  393. -- 3) kleiner rotierender Sweep fuer Nicht-Rednet-Kanaele
  394. for i = 0, CONFIG.sweepWindow - 1 do
  395. list[#list + 1] = (radio.sweepPos + i) % 65534
  396. end
  397. radio.sweepPos = (radio.sweepPos + CONFIG.sweepWindow) % 65534
  398. -- 4) wenige niedrige IDs als Catch-all
  399. for ch = 0, math.max(0, CONFIG.lowFill - 1) do list[#list + 1] = ch end
  400. return list
  401. end
  402.  
  403. radio.applyPlan = function(desiredList)
  404. if not radio.dev then return end
  405. local desired = {}
  406. local n = 0
  407. for _, ch in ipairs(desiredList) do
  408. if not desired[ch] then
  409. n = n + 1
  410. if n > radio.budget then break end -- harte Kappung am aktuellen Budget
  411. desired[ch] = true
  412. end
  413. end
  414. for ch in pairs(radio.open) do
  415. if not desired[ch] then
  416. pcall(radio.dev.close, ch)
  417. radio.open[ch] = nil
  418. end
  419. end
  420. local count = 0
  421. for ch in pairs(desired) do
  422. if not radio.open[ch] then
  423. if pcall(radio.dev.open, ch) then radio.open[ch] = true end
  424. end
  425. if radio.open[ch] then count = count + 1 end
  426. end
  427. STATE.chOpen = count
  428. end
  429.  
  430. -- Empfangs-Governor: misst die Inbound-Rate und regelt Budget + Broadcast so,
  431. -- dass wir den Server NIE mit einer Kanal-Flut einfrieren. Slow-Start: klein
  432. -- anfangen und nur bei ruhiger Lage vorsichtig mehr Kanaele oeffnen; bei hoher
  433. -- Rate sofort Broadcast schliessen und Budget senken (Notbremse bei Panik).
  434. radio.govern = function()
  435. local dt = math.max(CONFIG.planInterval, 0.1)
  436. radio.rxRate = STATE.rxGov / dt
  437. STATE.rxGov = 0
  438. local ceil = math.min(radio.hwLimit, CONFIG.maxOpenChannels)
  439. local t = now()
  440. if radio.rxRate > CONFIG.panicRxPerSec then
  441. radio.holdBroadcast = false
  442. radio.bcastCooldownUntil = t + CONFIG.bcastCooldown
  443. radio.budget = math.max(4, math.min(radio.budget, 6))
  444. STATE.overloaded = true
  445. logMsg(string.format("!! UEBERLAST %.0f Nachr/s -> Notbremse (Broadcast zu, Budget %d)",
  446. radio.rxRate, radio.budget))
  447. elseif radio.rxRate > CONFIG.maxRxPerSec then
  448. radio.holdBroadcast = false
  449. radio.bcastCooldownUntil = t + CONFIG.bcastCooldown
  450. radio.budget = math.max(4, radio.budget - 2)
  451. STATE.overloaded = true
  452. else
  453. STATE.overloaded = false
  454. STATE.emergencyTripped = false -- Ruhe: Notbremse wieder scharf schalten
  455. if radio.budget < ceil then
  456. local step = (radio.rxRate < CONFIG.maxRxPerSec * 0.25) and 4 or 1
  457. radio.budget = math.min(ceil, radio.budget + step)
  458. end
  459. -- Broadcast bei ruhiger Lage OFFEN halten (Haupt-Entdeckungsweg fuer Knoten).
  460. -- Nach einer Ueberlast erst nach der Abkuehlzeit wieder, sonst Flattern.
  461. if CONFIG.autoBroadcast and t >= radio.bcastCooldownUntil then
  462. radio.holdBroadcast = true
  463. else
  464. radio.holdBroadcast = false
  465. end
  466. end
  467. end
  468.  
  469. -- Ereignis-Notbremse: wird DIREKT im Empfangspfad (onModemMessage) aufgerufen,
  470. -- also unabhaengig vom tick-getakteten Governor. Schliesst sofort die groessten
  471. -- Datenschleudern (Broadcast 65535 + GPS 65534). Das genuegt, weil der Rest
  472. -- (gezielte Kanaele) wenig Volumen hat; den Rest raeumt der naechste applyPlan.
  473. radio.emergencyBrake = function()
  474. STATE.overloaded = true
  475. STATE.emergencyTripped = true
  476. radio.holdBroadcast = false
  477. radio.bcastCooldownUntil = now() + CONFIG.bcastCooldown
  478. radio.budget = math.min(radio.budget, CONFIG.panicFloor)
  479. if radio.dev then
  480. pcall(radio.dev.close, 65535); radio.open[65535] = nil
  481. pcall(radio.dev.close, 65534); radio.open[65534] = nil
  482. end
  483. logMsg("!! NOTBREMSE: Broadcast/GPS sofort geschlossen (Empfangs-Sturm)")
  484. end
  485.  
  486. -- rate-limitiertes Senden (jeder Sendeweg laeuft hier durch)
  487. radio.send = function(channel, replyChannel, msg)
  488. if not guard.acquire() then return false end
  489. local ok = pcall(radio.dev.transmit, channel, replyChannel, msg)
  490. if ok then
  491. STATE.sentCount = STATE.sentCount + 1
  492. else
  493. STATE.sendErr = STATE.sendErr + 1
  494. end
  495. return ok
  496. end
  497.  
  498. ------------------------------------------------------------------
  499. -- RECON : passives Aufklaeren, Roster
  500. ------------------------------------------------------------------
  501. recon = {}
  502.  
  503. recon.node = function(id)
  504. local nd = STATE.roster[id]
  505. if not nd then
  506. -- Roster-Deckel: schuetzt gegen eine Flut gefaelschter Sender-IDs (Gegner
  507. -- spoofen ggf. tausende IDs) und begrenzt Speicher/Sortierkosten.
  508. if STATE.nodeCount >= CONFIG.rosterCap then return nil end
  509. nd = {
  510. id = id, first = now(), last = now(), count = 0,
  511. asSender = 0, asTarget = 0, channels = {}, samples = {},
  512. dist = nil, mtype = nil, status = "active",
  513. downSince = nil, baselineSeen = false, lastTargeted = nil,
  514. }
  515. STATE.roster[id] = nd
  516. STATE.nodeIds[#STATE.nodeIds + 1] = id
  517. STATE.nodeCount = STATE.nodeCount + 1
  518. if not STATE.overloaded then logMsg("Neuer Knoten: ID " .. id) end
  519. end
  520. return nd
  521. end
  522.  
  523. recon.pushSample = function(nd, message)
  524. local s = nd.samples
  525. s[#s + 1] = message
  526. while #s > 5 do table.remove(s, 1) end
  527. end
  528.  
  529. recon.onModemMessage = function(side, channel, replyChannel, message, distance)
  530. STATE.rxCount = STATE.rxCount + 1
  531. STATE.rxWindow = STATE.rxWindow + 1
  532. STATE.rxGov = STATE.rxGov + 1
  533. -- Ereignis-Notbremse: laeuft im Empfangspfad selbst und greift daher auch,
  534. -- wenn der tick-getaktete Governor wegen Lag gerade nicht drankommt. rxGov
  535. -- wird vom Governor jede Sekunde genullt; ueberschreitet es hier den Burst-
  536. -- Schwellwert, ist der Empfang aus dem Ruder -> Broadcast/GPS sofort zu.
  537. if STATE.rxGov >= CONFIG.panicRxBurst and not STATE.emergencyTripped then
  538. radio.emergencyBrake()
  539. end
  540. -- Unter Ueberlast nur Stichproben verarbeiten, damit die Event-Queue schnell
  541. -- leerlaeuft (der Governor/die Notbremse schliesst parallel Kanaele).
  542. if STATE.overloaded and (STATE.rxCount % 8) ~= 0 then return end
  543. local t = now()
  544. -- Sender = replyChannel (falls plausible Computer-ID)
  545. if type(replyChannel) == "number" and replyChannel >= 0 and replyChannel < 65534 then
  546. local nd = recon.node(replyChannel)
  547. if nd then
  548. nd.last = t
  549. nd.count = nd.count + 1
  550. nd.asSender = nd.asSender + 1
  551. if type(channel) == "number" then
  552. nd.channels[channel] = (nd.channels[channel] or 0) + 1
  553. end
  554. if type(distance) == "number" then
  555. nd.dist = distance
  556. STATE.maxDist = math.max(STATE.maxDist or 0, distance)
  557. end
  558. nd.mtype = type(message)
  559. recon.pushSample(nd, message)
  560. if nd.count >= CONFIG.baselineMsgs then nd.baselineSeen = true end
  561. if nd.status == "down?" then
  562. nd.status = "active"
  563. nd.downSince = nil
  564. logMsg("Knoten " .. replyChannel .. " wieder aktiv (Reboot?)")
  565. elseif nd.status == "DEAD" and nd.killedAt and (t - nd.killedAt) > 10 then
  566. -- bestaetigt getoeteter Gegner sendet wieder -> hat rebootet, erneut angreifen
  567. nd.status = "active"
  568. nd.downSince = nil
  569. logMsg("Knoten " .. replyChannel .. " ONLINE nach bestaetigtem Kill -> erneut im Visier!")
  570. end
  571. end
  572. end
  573. -- Ziel = channel (falls plausible Computer-ID, nicht Broadcast/GPS)
  574. if type(channel) == "number" and channel >= 0 and channel < 65534 then
  575. local tn = recon.node(channel)
  576. if tn then
  577. tn.asTarget = tn.asTarget + 1
  578. tn.lastTargeted = t
  579. end
  580. end
  581. end
  582.  
  583. -- Knoten nach letzter Aktivitaet (neueste zuerst). Ergebnis kurz gecacht, da
  584. -- die Funktion aus mehreren Schleifen mehrmals/s aufgerufen wird und die volle
  585. -- Sortierung (bis rosterCap=400) sonst unnoetig CPU frisst - gerade unter Last.
  586. recon.recentNodes = function()
  587. local t = now()
  588. if STATE.nodeCacheList and (t - (STATE.nodeCacheAt or -1)) < 0.5 then
  589. return STATE.nodeCacheList
  590. end
  591. local arr = {}
  592. for _, nd in pairs(STATE.roster) do arr[#arr + 1] = nd end
  593. table.sort(arr, function(a, b) return a.last > b.last end)
  594. STATE.nodeCacheList = arr
  595. STATE.nodeCacheAt = t
  596. return arr
  597. end
  598.  
  599. ------------------------------------------------------------------
  600. -- ATTACK : Spoof / Fuzz / Flood + Kampagnen-Scheduler
  601. ------------------------------------------------------------------
  602. attack = {}
  603.  
  604. attack.stateFor = function(id)
  605. local s = STATE.targets[id]
  606. if not s then
  607. s = { id = id, phase = "observe", phaseStart = now(), pokes = 0 }
  608. STATE.targets[id] = s
  609. end
  610. return s
  611. end
  612.  
  613. attack.randomSample = function()
  614. local pool = {}
  615. for _, nd in pairs(STATE.roster) do
  616. for _, sm in ipairs(nd.samples) do pool[#pool + 1] = sm end
  617. end
  618. if #pool == 0 then return nil end
  619. return pool[math.random(#pool)]
  620. end
  621.  
  622. -- eine fremde ID vortaeuschen (vom Ziel als Peer "vertraut")
  623. attack.spoofSource = function(targetId)
  624. local ids = STATE.nodeIds
  625. if #ids > 0 then
  626. for _ = 1, 6 do
  627. local cand = ids[math.random(#ids)]
  628. if cand ~= targetId and cand ~= STATE.myId then return cand end
  629. end
  630. end
  631. return math.random(0, 64)
  632. end
  633.  
  634. attack.FUZZ = {
  635. function() return "" end,
  636. function() return string.rep("A", math.random(200, 4000)) end,
  637. function() return math.random(-1000000000, 1000000000) end,
  638. function() return {} end,
  639. function() return { cmd = randstr(), id = math.random(0, 999), data = randstr() } end,
  640. function() return "{" .. randstr() end,
  641. function() return "}" .. randstr() .. "=" end,
  642. function() return true end,
  643. function()
  644. local s = attack.randomSample()
  645. if type(s) == "string" and #s > 1 then return string.sub(s, 1, #s - 1) end
  646. return randstr()
  647. end,
  648. function() return string.char(math.random(1, 31)) .. randstr() end,
  649. function()
  650. if textutils and textutils.serialize then
  651. local ok, r = pcall(textutils.serialize, { a = 1, b = { 2, 3 } })
  652. if ok then return r end
  653. end
  654. return randstr()
  655. end,
  656. }
  657.  
  658. attack.fuzzPayload = function()
  659. return attack.FUZZ[math.random(#attack.FUZZ)]()
  660. end
  661.  
  662. attack.spoofPayload = function()
  663. local s = attack.randomSample()
  664. if s ~= nil then return s end
  665. return "ping"
  666. end
  667.  
  668. attack.candidates = function()
  669. local arr = {}
  670. for _, nd in ipairs(recon.recentNodes()) do
  671. if nd.id ~= STATE.myId
  672. and not STATE.whitelist[nd.id]
  673. and nd.status ~= "DEAD"
  674. and (nd.baselineSeen or nd.asTarget > 0) then
  675. arr[#arr + 1] = nd.id
  676. end
  677. end
  678. return arr
  679. end
  680.  
  681. attack.advancePhase = function(s)
  682. local mode = STATE.aggression
  683. local el = now() - s.phaseStart
  684. if mode == "stealth" then
  685. s.phase = "spoof"
  686. elseif mode == "aggressive" then
  687. if s.phase == "observe" and el > 2 then s.phase = "fuzz"; s.phaseStart = now()
  688. elseif s.phase == "fuzz" and el > 5 then s.phase = "flood"; s.phaseStart = now() end
  689. else -- adaptive
  690. if s.phase == "observe" and el > CONFIG.observeSecs then s.phase = "spoof"; s.phaseStart = now()
  691. elseif s.phase == "spoof" and el > CONFIG.spoofSecs then s.phase = "fuzz"; s.phaseStart = now()
  692. elseif s.phase == "fuzz" and el > CONFIG.fuzzSecs then s.phase = "flood"; s.phaseStart = now() end
  693. end
  694. end
  695.  
  696. attack.assess = function(nd)
  697. if nd.baselineSeen and nd.status == "active" and (now() - nd.last) > CONFIG.downSilence then
  698. nd.status = "down?"
  699. nd.downSince = now()
  700. logMsg(string.format("ZIEL %d still seit %.0fs -> VERDACHT Absturz. Sichtbaren Crash beim SL pruefen! [K]=bestaetigen",
  701. nd.id, now() - nd.last))
  702. end
  703. end
  704.  
  705. attack.batchFor = function(s)
  706. if s.phase == "observe" then return 0
  707. elseif s.phase == "spoof" then return 2
  708. elseif s.phase == "fuzz" then return 4
  709. else return CONFIG.floodEnabled and 20 or 4 end
  710. end
  711.  
  712. attack.emit = function(nd, s)
  713. local src = attack.spoofSource(nd.id)
  714. local payload
  715. if s.phase == "spoof" then
  716. payload = attack.spoofPayload()
  717. else
  718. payload = attack.fuzzPayload()
  719. end
  720. radio.send(nd.id, src, payload)
  721. -- Broadcast-Stoerung nur mit explizitem Opt-in: ein Send auf 65535 wird vom
  722. -- Server an JEDEN Computer mit offenem Broadcast-Kanal verteilt (Fan-out ->
  723. -- server-weite Last, genau die Flut, die wir vermeiden wollen).
  724. if s.phase == "flood" and CONFIG.floodBroadcast and math.random() < 0.5 then
  725. radio.send(65535, src, payload)
  726. end
  727. s.pokes = s.pokes + 1
  728. end
  729.  
  730. attack.step = function()
  731. if not STATE.campaignOn or STATE.attacksPaused then return end
  732. local ids = attack.candidates()
  733. if #ids == 0 then return end
  734. STATE.rr = (STATE.rr % #ids) + 1
  735. local id = ids[STATE.rr]
  736. local nd = STATE.roster[id]
  737. if not nd then return end
  738. local s = attack.stateFor(id)
  739. attack.advancePhase(s)
  740. attack.assess(nd)
  741. local batch = attack.batchFor(s)
  742. if nd.status == "down?" then batch = math.min(batch, 2) end -- nur nachfassen
  743. for i = 1, batch do
  744. if not STATE.running then return end
  745. attack.emit(nd, s)
  746. if i % 30 == 0 then sleep(0) end
  747. end
  748. end
  749.  
  750. ------------------------------------------------------------------
  751. -- UI : Dashboard (Computer-Schirm + optional Monitor)
  752. ------------------------------------------------------------------
  753. ui = { monSide = nil }
  754.  
  755. ui.initDisplays = function()
  756. STATE.displays = {}
  757. local termColour = false
  758. pcall(function() termColour = term.isColour() end)
  759. STATE.displays[1] = { dev = term, colour = termColour, name = "term" }
  760. if ui.monSide and peripheral.isPresent(ui.monSide) then
  761. local m = peripheral.wrap(ui.monSide)
  762. pcall(function() m.setTextScale(0.5) end)
  763. local c = false
  764. pcall(function() c = m.isColour() end)
  765. STATE.displays[#STATE.displays + 1] = { dev = m, colour = c, name = "mon" }
  766. end
  767. end
  768.  
  769. ui.viewList = function()
  770. return recon.recentNodes()
  771. end
  772.  
  773. ui.draw = function(d)
  774. local dev = d.dev
  775. local ok, w, h = pcall(dev.getSize)
  776. if not ok or not w then return end
  777. if d.colour then
  778. pcall(dev.setBackgroundColour, colours.black)
  779. pcall(dev.setTextColour, colours.white)
  780. end
  781. pcall(dev.clear)
  782.  
  783. local function put(x, y, txt, fg)
  784. if y < 1 or y > h then return end
  785. if x > w then return end
  786. txt = tostring(txt)
  787. if d.colour then pcall(dev.setTextColour, fg or colours.white) end
  788. pcall(dev.setCursorPos, x, y)
  789. pcall(dev.write, string.sub(txt, 1, w - x + 1))
  790. end
  791.  
  792. local tps = guard.tps and string.format("%.1f", guard.tps) or "--"
  793. local camp = STATE.campaignOn and (STATE.attacksPaused and "PAUSE" or "AKTIV") or "AUS"
  794. put(1, 1, "== REDNET EW == ID:" .. STATE.myId .. " " .. gameClock(), colours.cyan)
  795. put(1, 2, "Kampagne:" .. camp .. " Modus:" .. STATE.aggression
  796. .. " Flood:" .. (CONFIG.floodEnabled and "an" or "aus"),
  797. STATE.campaignOn and colours.lime or colours.grey)
  798. put(1, 3, "TPS:" .. tps .. "(" .. guard.mode .. ")"
  799. .. (guard.throttled and " DROSSEL" or "")
  800. .. " Rate:" .. string.format("%.0f", guard.rate) .. "/s",
  801. guard.throttled and colours.orange or colours.white)
  802. put(1, 4, "Knoten:" .. STATE.nodeCount .. " RX:" .. STATE.rxCount
  803. .. " TX:" .. STATE.sentCount .. " Reichw~:"
  804. .. (STATE.maxDist and (math.floor(STATE.maxDist) .. "m") or "?")
  805. .. " Kills:" .. STATE.killConfirmed, colours.white)
  806. put(1, 5, "Kanaele:" .. (STATE.chOpen or 0) .. "/" .. radio.budget
  807. .. " rx/s:" .. math.floor(radio.rxRate or 0)
  808. .. (radio.holdBroadcast and " [BC]" or "")
  809. .. (STATE.overloaded and " !!UEBERLAST-DROSSEL!!" or ""),
  810. STATE.overloaded and colours.red or colours.lightGrey)
  811.  
  812. put(1, 6, " ID alt msgs ch typ status", colours.lightGrey)
  813.  
  814. local list = ui.viewList()
  815. STATE.viewList = list
  816. if STATE.selIndex < 1 then STATE.selIndex = 1 end
  817. if STATE.selIndex > #list and #list > 0 then STATE.selIndex = #list end
  818.  
  819. local rowStart = 7
  820. -- Log-Hoehe an die verfuegbare Hoehe anpassen, damit auf kleinen Monitoren
  821. -- Roster-Zeilen sichtbar bleiben und der Log nicht die Kopfzeile ueberschreibt.
  822. local logLines = math.max(1, math.min(5, h - rowStart - 1))
  823. local rowEnd = h - logLines - 1
  824. local maxRows = rowEnd - rowStart + 1
  825. if maxRows < 0 then maxRows = 0 end
  826.  
  827. -- Scroll-Fenster um die Auswahl
  828. local first = 1
  829. if #list > maxRows and maxRows > 0 then
  830. first = math.max(1, math.min(STATE.selIndex - math.floor(maxRows / 2), #list - maxRows + 1))
  831. end
  832.  
  833. local y = rowStart
  834. for i = first, math.min(#list, first + maxRows - 1) do
  835. local nd = list[i]
  836. local st = nd.status
  837. if STATE.whitelist[nd.id] then st = "WL" end
  838. local col = colours.white
  839. if st == "down?" then col = colours.yellow
  840. elseif st == "DEAD" then col = colours.lime
  841. elseif st == "WL" then col = colours.grey end
  842. local marker = (i == STATE.selIndex) and ">" or " "
  843. local line = string.format("%s %-5d %3ds %5d %2d %-6s %s",
  844. marker, nd.id, math.floor(now() - nd.last), nd.count,
  845. cntChannels(nd), truncate(nd.mtype or "?", 6), st)
  846. put(1, y, line, (i == STATE.selIndex) and colours.yellow or col)
  847. y = y + 1
  848. end
  849.  
  850. -- Log unten
  851. local ly = h - logLines
  852. put(1, ly, "-- Log --------------------------------------------", colours.grey)
  853. local L = STATE.log
  854. for i = 1, logLines - 1 do
  855. local idx = #L - (logLines - 1) + i
  856. if idx >= 1 then put(1, ly + i, L[idx], colours.lightGrey) end
  857. end
  858. local help
  859. if w >= 78 then
  860. help = "[S]tart [Spc]Pause [A]Modus [F]lood [B]cast [+/-]Rate [W]L [K]ill [R]escan [Q]uit"
  861. else
  862. help = "S:Start Spc:Pause A:Mode F:Fl B:BC W:WL K:Kill Q"
  863. end
  864. put(1, h, help, colours.cyan)
  865. end
  866.  
  867. ui.render = function()
  868. for _, d in ipairs(STATE.displays) do pcall(ui.draw, d) end
  869. end
  870.  
  871. ------------------------------------------------------------------
  872. -- INPUT : Tastensteuerung (nur am Computer)
  873. ------------------------------------------------------------------
  874. input = {}
  875.  
  876. input.selectedNode = function()
  877. local l = STATE.viewList
  878. if l and l[STATE.selIndex] then return l[STATE.selIndex] end
  879. return nil
  880. end
  881.  
  882. input.onChar = function(c)
  883. c = string.lower(c)
  884. if c == "q" then
  885. STATE.running = false
  886. elseif c == " " then
  887. STATE.attacksPaused = not STATE.attacksPaused
  888. logMsg("Angriffe " .. (STATE.attacksPaused and "pausiert" or "fortgesetzt"))
  889. elseif c == "s" then
  890. STATE.campaignOn = not STATE.campaignOn
  891. logMsg("Kampagne " .. (STATE.campaignOn and "GESTARTET" or "gestoppt"))
  892. elseif c == "a" then
  893. local order = { stealth = "adaptive", adaptive = "aggressive", aggressive = "stealth" }
  894. STATE.aggression = order[STATE.aggression] or "adaptive"
  895. logMsg("Angriffs-Modus: " .. STATE.aggression)
  896. elseif c == "f" then
  897. CONFIG.floodEnabled = not CONFIG.floodEnabled
  898. logMsg("Flooding " .. (CONFIG.floodEnabled and "an" or "aus"))
  899. elseif c == "b" then
  900. -- Broadcast sofort oeffnen (Abkuehlzeit aufheben). Die Notbremse kann ihn
  901. -- bei echter Flut trotzdem wieder schliessen.
  902. radio.bcastCooldownUntil = 0
  903. radio.holdBroadcast = true
  904. logMsg("Broadcast manuell geoeffnet")
  905. elseif c == "]" or c == "+" or c == "=" then
  906. guard.baseRate = math.min(guard.baseRate + 5, 200)
  907. if not guard.throttled then guard.rate = guard.baseRate end
  908. logMsg("Basis-Rate: " .. guard.baseRate .. "/s")
  909. elseif c == "[" or c == "-" then
  910. guard.baseRate = math.max(guard.baseRate - 5, 1)
  911. if not guard.throttled then guard.rate = guard.baseRate end
  912. logMsg("Basis-Rate: " .. guard.baseRate .. "/s")
  913. elseif c == "w" then
  914. local nd = input.selectedNode()
  915. if nd then
  916. STATE.whitelist[nd.id] = not STATE.whitelist[nd.id] or nil
  917. logMsg("Whitelist " .. nd.id .. ": " .. (STATE.whitelist[nd.id] and "an" or "aus"))
  918. end
  919. elseif c == "k" then
  920. local nd = input.selectedNode()
  921. if nd and nd.status ~= "DEAD" then
  922. nd.status = "DEAD"
  923. nd.killedAt = now()
  924. STATE.killConfirmed = STATE.killConfirmed + 1
  925. logMsg("KILL bestaetigt: Knoten " .. nd.id .. " (sichtbarer Crash)")
  926. end
  927. elseif c == "r" then
  928. logMsg("Rescan: Kanal-Limit + Plan neu")
  929. pcall(function()
  930. radio.probeLimit()
  931. radio.applyPlan(radio.buildDesired())
  932. end)
  933. end
  934. end
  935.  
  936. input.onKey = function(code)
  937. if code == keys.up then
  938. STATE.selIndex = math.max(1, STATE.selIndex - 1)
  939. elseif code == keys.down then
  940. STATE.selIndex = STATE.selIndex + 1
  941. elseif code == keys.right then
  942. guard.baseRate = math.min(guard.baseRate + 5, 200)
  943. if not guard.throttled then guard.rate = guard.baseRate end
  944. elseif code == keys.left then
  945. guard.baseRate = math.max(guard.baseRate - 5, 1)
  946. if not guard.throttled then guard.rate = guard.baseRate end
  947. end
  948. end
  949.  
  950. ------------------------------------------------------------------
  951. -- SCOUT : optionale OpenCCSensors-Ortung (best effort)
  952. ------------------------------------------------------------------
  953. scout = { side = nil, hasTargets = false }
  954.  
  955. scout.find = function()
  956. for _, side in ipairs(rs.getSides()) do
  957. if peripheral.isPresent(side) and peripheral.getType(side) == "sensor" then
  958. scout.side = side
  959. local methods = {}
  960. pcall(function() methods = peripheral.getMethods(side) end)
  961. for _, m in ipairs(methods or {}) do
  962. if m == "getTargets" then scout.hasTargets = true end
  963. end
  964. return side
  965. end
  966. end
  967. return nil
  968. end
  969.  
  970. scout.poll = function()
  971. if not scout.side or not scout.hasTargets then return end
  972. local ok, targets = pcall(peripheral.call, scout.side, "getTargets")
  973. if ok and type(targets) == "table" then
  974. local n = 0
  975. for _ in pairs(targets) do n = n + 1 end
  976. STATE.scoutCount = n
  977. end
  978. end
  979.  
  980. ------------------------------------------------------------------
  981. -- Nebenlaeufige Schleifen
  982. ------------------------------------------------------------------
  983. eventLoop = function()
  984. while STATE.running do
  985. local ev = { os.pullEventRaw() }
  986. local e = ev[1]
  987. if e == "modem_message" then
  988. pcall(recon.onModemMessage, ev[2], ev[3], ev[4], ev[5], ev[6])
  989. elseif e == "char" then
  990. pcall(input.onChar, ev[2])
  991. elseif e == "key" then
  992. pcall(input.onKey, ev[2])
  993. elseif e == "terminate" then
  994. STATE.running = false
  995. end
  996. if not STATE.running then return end
  997. end
  998. end
  999.  
  1000. attackLoop = function()
  1001. while STATE.running do
  1002. local ok, err = pcall(attack.step)
  1003. if not ok then logMsg("attack-Fehler: " .. tostring(err)) end
  1004. sleep(CONFIG.attackTick)
  1005. end
  1006. end
  1007.  
  1008. guardLoop = function()
  1009. pcall(guard.detect)
  1010. while STATE.running do
  1011. local ok, err = pcall(guard.tick)
  1012. if not ok then
  1013. logMsg("guard-Fehler: " .. tostring(err))
  1014. sleep(2)
  1015. end
  1016. end
  1017. end
  1018.  
  1019. uiLoop = function()
  1020. while STATE.running do
  1021. pcall(ui.render)
  1022. sleep(CONFIG.uiRefresh)
  1023. end
  1024. end
  1025.  
  1026. scoutLoop = function()
  1027. while STATE.running do
  1028. pcall(scout.poll)
  1029. sleep(3)
  1030. end
  1031. end
  1032.  
  1033. planLoop = function()
  1034. while STATE.running do
  1035. pcall(function()
  1036. radio.govern() -- Empfangsrate messen + Budget/Broadcast regeln
  1037. radio.applyPlan(radio.buildDesired()) -- Kanaele entsprechend oeffnen/schliessen
  1038. end)
  1039. sleep(CONFIG.planInterval)
  1040. end
  1041. end
  1042.  
  1043. ------------------------------------------------------------------
  1044. -- SETUP-ASSISTENT
  1045. ------------------------------------------------------------------
  1046. -- gibt true zurueck (Taste) oder false (terminate/Ctrl+T). os.pullEventRaw
  1047. -- wirft bei terminate nicht, damit wir sauber aufraeumen koennen.
  1048. local function pressAnyKey()
  1049. print("")
  1050. print(" [ Taste druecken zum Fortfahren ]")
  1051. while true do
  1052. local e = os.pullEventRaw()
  1053. if e == "key" then return true end
  1054. if e == "terminate" then return false end
  1055. end
  1056. end
  1057.  
  1058. setupWizard = function()
  1059. term.clear(); term.setCursorPos(1, 1)
  1060. local function line(s) print(s) end
  1061. local function abort() -- bei Ctrl+T: offene Kanaele schliessen, sauber raus
  1062. pcall(function() if radio.dev then radio.dev.closeAll() end end)
  1063. return false
  1064. end
  1065. line("=====================================================")
  1066. line(" REDNET ELECTRONIC-WARFARE - AUFBAU / SETUP")
  1067. line("=====================================================")
  1068. line("")
  1069. line("Platziere folgende Bloecke an den Computer:")
  1070. line("")
  1071. line(" 1) WIRELESS MODEM (Pflicht) an eine beliebige Seite.")
  1072. line(" Fuer maximale Reichweite den Computer moeglichst")
  1073. line(" HOCH bauen - Reichweite steigt mit der Hoehe.")
  1074. line("")
  1075. line(" 2) ADVANCED MONITOR (optional, empfohlen) an eine")
  1076. line(" andere Seite - grosses Live-Dashboard. Mehrere")
  1077. line(" Bloecke breit/hoch = mehr Uebersicht.")
  1078. line("")
  1079. line(" 3) OPENCCSENSORS-SENSOR (optional) fuer Gegner-Ortung.")
  1080. line("")
  1081. line(" Hinweis: HTTP wird fuer 'pastebin get' und die echte")
  1082. line(" TPS-Messung gebraucht. Ohne HTTP laeuft der Guard im")
  1083. line(" Selbst-Limit-Modus (Fallback).")
  1084. line("")
  1085. line(" Empfang: bei ruhiger Lage bleibt Broadcast offen")
  1086. line(" (findet Knoten). Steigt die Empfangsrate zu stark,")
  1087. line(" schliesst eine Notbremse Broadcast/GPS sofort -")
  1088. line(" so friert die Funk-Flut den Server nicht ein.")
  1089. if not pressAnyKey() then return abort() end
  1090.  
  1091. term.clear(); term.setCursorPos(1, 1)
  1092. line("== Peripherie-Erkennung ==")
  1093. line("")
  1094. local modem = radio.findModem()
  1095. local monSide = nil
  1096. local sensor = nil
  1097. for _, side in ipairs(rs.getSides()) do
  1098. if peripheral.isPresent(side) then
  1099. local t = peripheral.getType(side)
  1100. if t == "monitor" and not monSide then monSide = side end
  1101. if t == "sensor" and not sensor then sensor = side end
  1102. end
  1103. end
  1104. ui.monSide = monSide
  1105. if sensor then scout.find() end
  1106.  
  1107. line(" Modem : " .. (modem and ("[OK] " .. modem) or "[FEHLT]"))
  1108. line(" Monitor : " .. (monSide and ("[OK] " .. monSide) or "[-- kein]"))
  1109. line(" Sensor : " .. (sensor and ("[OK] " .. sensor) or "[-- kein]"))
  1110. line(" HTTP : " .. (http and "[verfuegbar]" or "[aus]"))
  1111. line("")
  1112.  
  1113. if not modem then
  1114. line("FEHLER: Kein Wireless Modem gefunden!")
  1115. line("Setze ein Wireless Modem an eine Seite des Computers")
  1116. line("und starte 'ew' erneut.")
  1117. pressAnyKey()
  1118. return false
  1119. end
  1120.  
  1121. line("Kalibriere Kanal-Limit ...")
  1122. local maxc = radio.probeLimit()
  1123. line(" -> Modem-Limit ~" .. maxc .. "; Empfang rate-geregelt")
  1124. line(" (Start " .. CONFIG.startChannels .. " Kanaele, max " .. CONFIG.maxOpenChannels
  1125. .. ", Budget " .. CONFIG.maxRxPerSec .. " Nachr/s)")
  1126. radio.holdBroadcast = true -- fuer den Hoertest Broadcast mitlauschen
  1127. radio.applyPlan(radio.buildDesired())
  1128.  
  1129. line("Hoere 6s auf Funkverkehr (Reichweiten-/Aktivitaetstest) ...")
  1130. local timer = os.startTimer(6)
  1131. while true do
  1132. local ev = { os.pullEventRaw() }
  1133. if ev[1] == "modem_message" then
  1134. pcall(recon.onModemMessage, ev[2], ev[3], ev[4], ev[5], ev[6])
  1135. elseif ev[1] == "timer" and ev[2] == timer then
  1136. break
  1137. elseif ev[1] == "terminate" then
  1138. return abort()
  1139. end
  1140. end
  1141. line(" -> Knoten gehoert: " .. STATE.nodeCount .. " Nachrichten: " .. STATE.rxCount)
  1142. if STATE.nodeCount == 0 then
  1143. line(" Noch nichts gehoert - normal: im Setup lauschen wir nur")
  1144. line(" auf wenige Kanaele. Im Betrieb tastet [B]cast/Governor")
  1145. line(" den Broadcast ab. Sonst hoeher bauen / [R]escan.")
  1146. else
  1147. line(" Max. Distanz bisher: " ..
  1148. (STATE.maxDist and (math.floor(STATE.maxDist) .. "m") or "?"))
  1149. end
  1150. line("")
  1151. line("WICHTIG: Setze deine EIGENEN Netz-IDs spaeter mit [W]")
  1152. line("auf die Whitelist, damit du sie nicht selbst angreifst!")
  1153. line("")
  1154. line("Kampagne startet NICHT automatisch - mit [S] beginnen.")
  1155. if not pressAnyKey() then return abort() end
  1156. return true
  1157. end
  1158.  
  1159. ------------------------------------------------------------------
  1160. -- MAIN
  1161. ------------------------------------------------------------------
  1162. main = function()
  1163. STATE.myId = os.getComputerID()
  1164. STATE.whitelist[STATE.myId] = true
  1165. local seed = 1
  1166. pcall(function() seed = math.floor((os.time() * 10000) + STATE.myId + (os.clock() * 1000)) end)
  1167. math.randomseed(seed)
  1168. guard.last = now()
  1169.  
  1170. if not setupWizard() then return end
  1171.  
  1172. ui.initDisplays()
  1173. STATE.running = true
  1174. STATE.campaignOn = CONFIG.autostart
  1175.  
  1176. -- pcall, damit ein unerwarteter Fehler in einer Schleife trotzdem ins
  1177. -- Aufraeumen faellt (Kanaele schliessen) statt roh zu crashen.
  1178. local ok, err = pcall(parallel.waitForAny,
  1179. eventLoop, attackLoop, guardLoop, uiLoop, scoutLoop, planLoop)
  1180.  
  1181. -- Aufraeumen
  1182. pcall(function() if radio.dev then radio.dev.closeAll() end end)
  1183. pcall(function()
  1184. if term.isColour() then
  1185. term.setBackgroundColour(colours.black)
  1186. term.setTextColour(colours.white)
  1187. end
  1188. end)
  1189. term.clear(); term.setCursorPos(1, 1)
  1190. print("ew beendet. Kanaele geschlossen.")
  1191. print("Kills bestaetigt: " .. STATE.killConfirmed)
  1192. if not ok and err and err ~= "" then
  1193. printError("Fehler: " .. tostring(err))
  1194. end
  1195. end
  1196.  
  1197. main()
  1198.  
Advertisement
Add Comment
Please, Sign In to add comment