├── ControlCenter └── startup.lua ├── LICENSE ├── Missile_Command_Computer └── startup.lua ├── OnCannon └── startup.lua ├── OnPhys └── startup.lua ├── OnYaw └── startup.lua ├── Only_CBC └── startup.lua ├── README.md └── command_computer_only └── startup.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 shao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Missile_Command_Computer/startup.lua: -------------------------------------------------------------------------------- 1 | peripheral.find("modem", rednet.open) 2 | local missile_protocol, request_protocol = "CBCMissileNetWork", "CBCcenter" 3 | local properties, system 4 | 5 | system = { 6 | fileName = "dat", 7 | file = nil 8 | } 9 | 10 | system.init = function() 11 | system.file = io.open(system.fileName, "r") 12 | if system.file then 13 | local tmpProp = textutils.unserialise(system.file:read("a")) 14 | properties = system.reset() 15 | for k, v in pairs(properties) do 16 | if tmpProp[k] then 17 | properties[k] = tmpProp[k] 18 | end 19 | end 20 | 21 | system.file:close() 22 | else 23 | properties = system.reset() 24 | system.updatePersistentData() 25 | end 26 | end 27 | 28 | system.reset = function() 29 | return { 30 | controlCenterId = "-1", 31 | password = "123456", 32 | shot_face = "down", 33 | offset = 1, 34 | speed = 6, 35 | shot_ct = 10, 36 | safe_time = 10, 37 | max_flying_time = 200, 38 | } 39 | end 40 | 41 | system.updatePersistentData = function() 42 | system.write(system.fileName, properties) 43 | end 44 | 45 | system.write = function(file, obj) 46 | system.file = io.open(file, "w") 47 | system.file:write(textutils.serialise(obj)) 48 | system.file:close() 49 | end 50 | 51 | system.init() 52 | 53 | local genStr = function(s, count) 54 | local result = "" 55 | for i = 1, count, 1 do 56 | result = result .. s 57 | end 58 | return result 59 | end 60 | 61 | local vector = {} 62 | local newVec = function (x, y, z) 63 | if type(x) == "table" then 64 | return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector }) 65 | elseif x and y and z then 66 | return setmetatable({ x = x, y = y, z = z}, { __index = vector}) 67 | else 68 | return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector}) 69 | end 70 | end 71 | 72 | function vector:zero() 73 | self.x = 0 74 | self.y = 0 75 | self.z = 0 76 | return self 77 | end 78 | 79 | function vector:copy() 80 | return newVec(self.x, self.y, self.z) 81 | end 82 | local square = function (num) 83 | return num * num 84 | end 85 | function vector:len() 86 | return math.sqrt(square(self.x) + square(self.y) + square(self.z) ) 87 | end 88 | 89 | function vector:norm() 90 | local l = self:len() 91 | if l == 0 then 92 | self:zero() 93 | else 94 | self.x = self.x / l 95 | self.y = self.y / l 96 | self.z = self.z / l 97 | end 98 | return self 99 | end 100 | 101 | function vector:nega() 102 | self.x = -self.x 103 | self.y = -self.y 104 | self.z = -self.z 105 | return self 106 | end 107 | 108 | function vector:add(v) 109 | self.x = self.x + v.x 110 | self.y = self.y + v.y 111 | self.z = self.z + v.z 112 | return self 113 | end 114 | 115 | function vector:sub(v) 116 | self.x = self.x - v.x 117 | self.y = self.y - v.y 118 | self.z = self.z - v.z 119 | return self 120 | end 121 | 122 | function vector:scale(num) 123 | self.x = self.x * num 124 | self.y = self.y * num 125 | self.z = self.z * num 126 | return self 127 | end 128 | 129 | function vector:unpack() 130 | return self.x, self.y, self.z 131 | end 132 | 133 | local vector_scale = function(v, s) 134 | return { 135 | x = v.x * s, 136 | y = v.y * s, 137 | z = v.z * s 138 | } 139 | end 140 | local unpackVec = function(v) 141 | return v.x, v.y, v.z 142 | end 143 | 144 | local quat = {} 145 | function quat.new() 146 | return { w = 1, x = 0, y = 0, z = 0} 147 | end 148 | 149 | function quat.vecRot(q, v) 150 | local x = q.x * 2 151 | local y = q.y * 2 152 | local z = q.z * 2 153 | local xx = q.x * x 154 | local yy = q.y * y 155 | local zz = q.z * z 156 | local xy = q.x * y 157 | local xz = q.x * z 158 | local yz = q.y * z 159 | local wx = q.w * x 160 | local wy = q.w * y 161 | local wz = q.w * z 162 | local res = {} 163 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 164 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 165 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 166 | return newVec(res.x, res.y, res.z) 167 | end 168 | 169 | function quat.multiply(q1, q2) 170 | local newQuat = {} 171 | newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w 172 | newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x 173 | newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y 174 | newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z 175 | return newQuat 176 | end 177 | 178 | function quat.nega(q) 179 | return { 180 | w = q.w, 181 | x = -q.x, 182 | y = -q.y, 183 | z = -q.z, 184 | } 185 | end 186 | local faces = { 187 | up = newVec(0, 1, 0), 188 | down = newVec(0, -1, 0), 189 | north = newVec(0, 0, -1), 190 | south = newVec(0, 0, 1), 191 | west = newVec(-1, 0, 0), 192 | east = newVec(1, 0, 0) 193 | } 194 | 195 | local controlCenter = { 196 | tgPos = newVec(), 197 | velocity = newVec(), 198 | mode = 2, 199 | fire = false 200 | } 201 | 202 | local ct = 20 203 | local listener = function() 204 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 205 | controlCenterId = controlCenterId and controlCenterId or 0 206 | while true do 207 | local id, msg = rednet.receive(missile_protocol, 2) 208 | if id == controlCenterId then 209 | controlCenter = msg 210 | ct = 20 211 | end 212 | end 213 | end 214 | 215 | local msnm = "cbc_missile" 216 | local sendRequest = function() 217 | while true do 218 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 219 | controlCenterId = controlCenterId and controlCenterId or 0 220 | rednet.send(controlCenterId, { 221 | name = msnm, 222 | pw = properties.password, 223 | }, request_protocol) 224 | sleep(0.05) 225 | end 226 | end 227 | 228 | local split = function(input, delimiter) 229 | input = tostring(input) 230 | delimiter = tostring(delimiter) 231 | if (delimiter == "") then return false end 232 | local pos, arr = 0, {} 233 | for st, sp in function() return string.find(input, delimiter, pos, true) end do 234 | table.insert(arr, string.sub(input, pos, st - 1)) 235 | pos = sp + 1 236 | end 237 | table.insert(arr, string.sub(input, pos)) 238 | return arr 239 | end 240 | 241 | local randomNumber = function() 242 | return math.random(0, 0xFFFFFFFF) 243 | end 244 | 245 | local stringToUUID = function(str) 246 | local timeStamp = os.epoch("local") 247 | local randomPart1 = randomNumber() 248 | local randomPart2 = randomNumber() 249 | 250 | return string.format("%08x-%04x-%04x-%04x-%012x", 251 | timeStamp % 0xFFFFFFFF, 252 | #str % 0xFFFF, 253 | randomPart1 % 0xFFFF, 254 | randomPart2 % 0xFFFF, 255 | randomPart1 + randomPart2 256 | ) 257 | end 258 | local genCaptcha = function(len) 259 | local length = len and len or 5 260 | local result = "" 261 | for i = 1, length, 1 do 262 | local num = math.random(0, 2) 263 | if num == 0 then 264 | result = result .. string.char(math.random(65, 90)) 265 | elseif num == 1 then 266 | result = result .. string.char(math.random(97, 122)) 267 | else 268 | result = result .. string.char(math.random(48, 57)) 269 | end 270 | end 271 | return result 272 | end 273 | 274 | local gen_uuid = function() 275 | return stringToUUID(genCaptcha()) 276 | end 277 | local genParticle = function(x, y, z) 278 | commands.execAsync(string.format("particle minecraft:cloud %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z)) 279 | end 280 | local block_offset = newVec(0.5, 0.5, 0.5) 281 | local selfPos = newVec(coordinate.getAbsoluteCoordinates()) 282 | local getMissileGenPos = function() 283 | local face_offset = newVec(faces[properties.shot_face]):scale(properties.offset) 284 | local wPos = newVec(ship.getWorldspacePosition()) 285 | local yardPos = newVec(ship.getShipyardPosition()) 286 | local offset = yardPos:sub(selfPos):sub(block_offset):sub(face_offset) 287 | offset = quat.vecRot(ship.getQuaternion(), offset) 288 | return wPos:sub(offset) 289 | end 290 | 291 | local flying_missiles = {} 292 | local absMissilEListener = { 293 | uuid = "" 294 | } 295 | 296 | local str_to_vec = function(str) 297 | if str then 298 | local n = split(str, ", ") 299 | local x, y, z = string.sub(n[1], 1, #n[1] - 1), string.sub(n[2], 1, #n[2] - 1), string.sub(n[3], 1, #n[3] - 1) 300 | 301 | return newVec(tonumber(x), tonumber(y), tonumber(z)) 302 | end 303 | end 304 | 305 | local normFuze = "{id:\"createbigcannons:timed_fuze\",tag:{FuzeTimer:20},Count:1b}" 306 | local proximity_fuze = "{id:\"createbigcannons:proximity_fuze\",tag:{DetonationDistance:1},Count:1b}" 307 | local targetPos = newVec() 308 | function absMissilEListener:run() 309 | local _, data = commands.exec(("data get entity @e[tag=%s, limit=1]"):format(self.uuid)) 310 | local motion = data[1]:match("Motion: %[([^%]]+)%]") 311 | self.time = self.time + 1 312 | if motion then 313 | motion = str_to_vec(motion) 314 | local pos = str_to_vec(data[1]:match("Pos: %[([^%]]+)%]")) 315 | 316 | local speed = properties.speed and properties.speed or 4 317 | local max_flying_time = properties.max_flying_time and math.abs(properties.max_flying_time) or 2000 318 | 319 | local tmp_pos = newVec(pos):add(newVec(motion):norm():scale(4)) 320 | local r_tg = newVec(targetPos):sub(pos) 321 | tmp_pos:add(newVec(r_tg):norm():scale(speed / 2)) 322 | 323 | local tg_motion = tmp_pos:sub(pos):norm():scale(speed) 324 | local tg_cmd = ("data modify entity @e[tag=%s, limit=1]"):format(self.uuid) 325 | commands.execAsync(("%s Motion set value [%.4fd,%.4fd,%.4fd]"):format(tg_cmd, tg_motion.x, tg_motion.y, tg_motion.z)) 326 | 327 | if self.time > max_flying_time or self.time > 5 and r_tg:len() < speed then 328 | commands.execAsync(("%s Fuze set value {id:\"createbigcannons:timed_fuze\",tag:{FuzeTimer:0},Count:1b}"):format(tg_cmd)) 329 | elseif self.time > properties.safe_time then 330 | local to_do = ("%s Fuze set value %s"):format(tg_cmd, proximity_fuze) 331 | commands.execAsync(to_do) 332 | end 333 | else 334 | --commands.execAsync(("say not found %s, remove it"):format(self.uuid)) 335 | flying_missiles[self.uuid] = nil 336 | end 337 | end 338 | 339 | local genMissileListenerThread = function(uuid) 340 | return setmetatable({ uuid = uuid, time = 0}, {__index = absMissilEListener}) 341 | end 342 | 343 | local missile_manager = function () 344 | while true do 345 | local to_run = {} 346 | for k, v in pairs(flying_missiles) do 347 | if v.run then 348 | table.insert(to_run, function() v:run() end) 349 | end 350 | end 351 | 352 | if #to_run > 0 then 353 | parallel.waitForAll(table.unpack(to_run)) 354 | else 355 | sleep(0.05) 356 | end 357 | end 358 | end 359 | 360 | local genMissile = function (pos, velocity) 361 | local uuid = gen_uuid() 362 | local str_pos = string.format("%0.2f %0.2f %0.2f", pos.x, pos.y, pos.z) 363 | local fuze = "Fuze:{id:\"createbigcannons:timed_fuze\",tag:{FuzeTimer:60},Count:1b}" 364 | local out_vec = faces[properties.shot_face] 365 | out_vec = quat.vecRot(ship.getQuaternion(), out_vec) 366 | local motion = string.format("Motion:[%.4fd,%.4fd,%.4fd]", out_vec.x, out_vec.y, out_vec.z) 367 | local str = ("summon createbigcannons:he_shell %s {Tags:[%s],NoGravity:1b,%s,%s}"):format(str_pos, uuid, motion, fuze) 368 | commands.execAsync(str) 369 | flying_missiles[uuid] = genMissileListenerThread(uuid) 370 | end 371 | 372 | local run_missile = function () 373 | local shot_ct = 0 374 | while true do 375 | if ct > 0 and controlCenter.omega then 376 | targetPos = newVec(controlCenter.tgPos):add(newVec(controlCenter.velocity):scale(2)) 377 | ct = ct - 1 378 | if controlCenter.missile then 379 | if shot_ct < 1 then 380 | --commands.execAsync(("say %d"):format(controlCenter.pos.x)) 381 | local ppos = getMissileGenPos() 382 | 383 | genMissile(ppos, newVec(controlCenter.center_velocity):scale(0.05)) 384 | --genParticle(ppos.x, ppos.y, ppos.z) 385 | shot_ct = properties.shot_ct 386 | end 387 | else 388 | shot_ct = shot_ct > 0 and shot_ct - 1 or 0 389 | end 390 | else 391 | controlCenter.missile = false 392 | end 393 | sleep(0.05) 394 | end 395 | end 396 | 397 | local termUtil = { 398 | cpX = 1, 399 | cpY = 1 400 | } 401 | 402 | local absTextField = { 403 | x = 1, 404 | y = 1, 405 | len = 15, 406 | text = "", 407 | textCorlor = "0", 408 | backgroundColor = "8" 409 | } 410 | 411 | function absTextField:paint() 412 | local str = "" 413 | for i = 1, self.len, 1 do 414 | local text = tostring(self.key[self.value]) 415 | local tmp = string.sub(text, i, i) 416 | if #tmp > 0 then 417 | str = str .. tmp 418 | else 419 | local tmp2 = "" 420 | for j = 0, self.len - i, 1 do 421 | tmp2 = tmp2 .. " " 422 | end 423 | str = str .. tmp2 424 | break 425 | end 426 | end 427 | 428 | term.setCursorPos(self.x, self.y) 429 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 430 | end 431 | 432 | function absTextField:inputChar(char) 433 | local xPos, yPos = term.getCursorPos() 434 | xPos = xPos + 1 - self.x 435 | local field = tostring(self.key[self.value]) 436 | if #field < self.len then 437 | if self.type == "number" then 438 | if char >= '0' and char <= '9' then 439 | if field == "0" then 440 | field = char 441 | else 442 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 443 | end 444 | 445 | self.key[self.value] = tonumber(field) 446 | termUtil.cpX = termUtil.cpX + 1 447 | end 448 | if char == '-' then 449 | self.key[self.value] = -self.key[self.value] 450 | end 451 | elseif self.type == "string" then 452 | local strEnd = string.sub(field, xPos, #field) 453 | field = string.sub(field, 1, xPos - 1) .. char .. strEnd 454 | self.key[self.value] = field 455 | termUtil.cpX = termUtil.cpX + 1 456 | end 457 | end 458 | end 459 | 460 | function absTextField:inputKey(key) 461 | local xPos, yPos = term.getCursorPos() 462 | local field = tostring(self.key[self.value]) 463 | local minXp = self.x 464 | local maxXp = minXp + #field 465 | if key == 259 or key == 261 then -- backSpace 466 | if xPos > minXp then 467 | termUtil.cpX = termUtil.cpX - 1 468 | if #field > 0 and termUtil.cpX > 1 then 469 | local index = termUtil.cpX - self.x 470 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 471 | end 472 | if self.type == "number" then 473 | local number = tonumber(field) 474 | if not number then 475 | self.key[self.value] = 0 476 | else 477 | self.key[self.value] = number 478 | end 479 | elseif self.type == "string" then 480 | self.key[self.value] = field 481 | end 482 | end 483 | elseif key == 257 or key == 335 then 484 | -- print("enter") 485 | elseif key == 262 or key == 263 then 486 | if key == 262 then 487 | termUtil.cpX = termUtil.cpX + 1 488 | elseif key == 263 then 489 | termUtil.cpX = termUtil.cpX - 1 490 | end 491 | elseif key == 264 or key == 258 then 492 | -- print("down") 493 | elseif key == 265 then 494 | -- print("up") 495 | end 496 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 497 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 498 | end 499 | 500 | function absTextField:click(x, y) 501 | local xPos = self.x 502 | if x >= xPos then 503 | if x < xPos + #tostring(self.key[self.value]) then 504 | termUtil.cpX, termUtil.cpY = x, y 505 | else 506 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 507 | end 508 | end 509 | end 510 | 511 | local newTextField = function(key, value, x, y) 512 | return setmetatable({ 513 | key = key, 514 | value = value, 515 | type = type(key[value]), 516 | x = x, 517 | y = y 518 | }, { 519 | __index = absTextField 520 | }) 521 | end 522 | 523 | local absSelectBox = { 524 | x = 1, 525 | y = 1, 526 | label = "", 527 | contents = {}, 528 | count = 0, 529 | interval = 0, 530 | fontColor = "8", 531 | backgroundColor = "f", 532 | selectColor = "e" 533 | } 534 | 535 | function absSelectBox:paint() 536 | term.setCursorPos(self.x, self.y) 537 | local select = tostring(self.key[self.value]) 538 | for i = 1, #self.contents, 1 do 539 | local str = tostring(self.contents[i]) 540 | if select == str then 541 | term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str)) 542 | else 543 | term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str)) 544 | end 545 | for j = 1, self.interval, 1 do 546 | term.write(" ") 547 | end 548 | end 549 | end 550 | 551 | function absSelectBox:click(x, y) 552 | local xPos = x + 1 - self.x 553 | local index = 0 554 | for i = 1, #self.contents, 1 do 555 | if xPos >= index and xPos <= index + #tostring(self.contents[i]) then 556 | self.key[self.value] = self.contents[i] 557 | break 558 | end 559 | index = index + #tostring(self.contents[i]) + self.interval 560 | end 561 | end 562 | 563 | local newSelectBox = function(key, value, interval, x, y, ...) 564 | return setmetatable({ 565 | key = key, 566 | value = value, 567 | interval = interval, 568 | x = x, 569 | y = y, 570 | type = type(key[value]), 571 | contents = {...} 572 | }, { 573 | __index = absSelectBox 574 | }) 575 | end 576 | 577 | local absSlider = { 578 | x = 1, 579 | y = 1, 580 | min = 0, 581 | max = 1, 582 | len = 20, 583 | fontColor = "8", 584 | backgroundColor = "f" 585 | } 586 | 587 | function absSlider:paint() 588 | local field = self.key[self.value] 589 | if field == "-" then 590 | field = 0 591 | end 592 | local maxVal = self.max - self.min 593 | local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5) 594 | xPos = xPos < 1 and 1 or xPos 595 | term.setCursorPos(self.x, self.y) 596 | for i = 1, self.len, 1 do 597 | if xPos == i then 598 | term.blit(" ", self.backgroundColor, self.fontColor) 599 | else 600 | term.blit("-", self.fontColor, self.backgroundColor) 601 | end 602 | end 603 | end 604 | 605 | function absSlider:click(x, y) 606 | local xPos = x + 1 - self.x 607 | if xPos > self.len then 608 | xPos = self.len 609 | end 610 | self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min 611 | end 612 | 613 | local newSlider = function(key, value, min, max, len, x, y) 614 | return setmetatable({ 615 | key = key, 616 | value = value, 617 | min = min, 618 | max = max, 619 | len = len, 620 | x = x, 621 | y = y 622 | }, { 623 | __index = absSlider 624 | }) 625 | end 626 | 627 | local selfId = os.getComputerID() 628 | local runTerm = function() 629 | local fieldTb = { 630 | offset = newTextField(properties, "offset", 12, 5), 631 | speed = newTextField(properties, "speed", 12, 7), 632 | shot_ct = newTextField(properties, "shot_ct", 12, 9), 633 | max_flying_time = newTextField(properties, "max_flying_time", 20, 11), 634 | password = newTextField(properties, "password", 12, 13), 635 | controlCenterId = newTextField(properties, "controlCenterId", 19, 15) 636 | } 637 | fieldTb.controlCenterId.len = 5 638 | fieldTb.password.len = 14 639 | fieldTb.offset.len = 5 640 | fieldTb.speed.len = 5 641 | fieldTb.shot_ct.len = 5 642 | fieldTb.max_flying_time.len = 10 643 | local selectBoxTb = { 644 | shot_face = newSelectBox(properties, "shot_face", 1, 12, 3, "up", "down", "north", "south", "west", "east"), 645 | } 646 | 647 | local alarm_id = os.setAlarm(os.time() + 0.05) 648 | local alarm_flag = false 649 | term.clear() 650 | term.setCursorPos(15, 8) 651 | term.write("Press any key to continue") 652 | while true do 653 | local eventData = {os.pullEvent()} 654 | local event = eventData[1] 655 | if event == "mouse_up" or event == "key_up" or event == "alarm" or event == "mouse_click" or event == 656 | "mouse_drag" or event == "key" or event == "char" then 657 | if not alarm_flag then 658 | alarm_flag = true 659 | else 660 | term.clear() 661 | term.setCursorPos(16, 1) 662 | printError(string.format("self id: %d", selfId)) 663 | 664 | term.setCursorPos(2, 3) 665 | term.write("SHOT_ON: ") 666 | term.setCursorPos(2, 5) 667 | term.write("OFFSET: ") 668 | term.setCursorPos(2, 7) 669 | term.write("SPEED: ") 670 | term.setCursorPos(2, 9) 671 | term.write("SHOT_CT: ") 672 | term.setCursorPos(2, 11) 673 | term.write("MAX_FLYING_TIME: ") 674 | 675 | term.setCursorPos(2, 13) 676 | term.write("Password: ") 677 | term.setCursorPos(2, 15) 678 | term.write("controlCenterId: ") 679 | 680 | for k, v in pairs(fieldTb) do 681 | v:paint() 682 | end 683 | 684 | for k, v in pairs(selectBoxTb) do 685 | v:paint() 686 | end 687 | 688 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 689 | 690 | if event == "mouse_click" then 691 | term.setCursorBlink(true) 692 | local x, y = eventData[3], eventData[4] 693 | for k, v in pairs(fieldTb) do -- 点击了输入框 694 | if y == v.y and x >= v.x and x <= v.x + v.len then 695 | v:click(x, y) 696 | end 697 | end 698 | for k, v in pairs(selectBoxTb) do -- 点击了选择框 699 | if y == v.y then 700 | v:click(x, y) 701 | end 702 | end 703 | elseif event == "key" then 704 | local key = eventData[2] 705 | for k, v in pairs(fieldTb) do 706 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 707 | v:inputKey(key) 708 | end 709 | end 710 | elseif event == "char" then 711 | local char = eventData[2] 712 | for k, v in pairs(fieldTb) do 713 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 714 | v:inputChar(char) 715 | end 716 | end 717 | end 718 | 719 | -- 刷新数据到properties 720 | system.updatePersistentData() 721 | end 722 | end 723 | end 724 | end 725 | 726 | parallel.waitForAll(run_missile, runTerm, listener, sendRequest, missile_manager) 727 | -------------------------------------------------------------------------------- /OnCannon/startup.lua: -------------------------------------------------------------------------------- 1 | peripheral.find("modem", rednet.open) 2 | 3 | local properties, system 4 | local protocol, request_protocol = "CBCNetWork", "CBCcenter" 5 | 6 | ----------------init----------------- 7 | local ANGLE_TO_SPEED = 26.6666666666666666667 8 | 9 | system = { 10 | fileName = "dat", 11 | file = nil 12 | } 13 | 14 | system.init = function() 15 | system.file = io.open(system.fileName, "r") 16 | if system.file then 17 | local tmpProp = textutils.unserialise(system.file:read("a")) 18 | properties = system.reset() 19 | for k, v in pairs(properties) do 20 | if tmpProp[k] then 21 | properties[k] = tmpProp[k] 22 | end 23 | end 24 | 25 | system.file:close() 26 | else 27 | properties = system.reset() 28 | system.updatePersistentData() 29 | end 30 | end 31 | 32 | system.reset = function() 33 | return { 34 | cannonName = "CBC", 35 | phyBearID = "-1", 36 | controlCenterId = "-1", 37 | mode = "hms", 38 | power_on = "front", -- 开机信号 39 | fire = "back", -- 开火信号 40 | cannonOffset = { 41 | x = 0, 42 | y = 3, 43 | z = 0 44 | }, 45 | minPitchAngle = -45, 46 | face = "west", 47 | idleFace = "west", 48 | password = "123456", 49 | InvertYaw = false, 50 | InvertPitch = false, 51 | max_rotate_speed = 256, 52 | lock_yaw_range = "0", 53 | lock_yaw_face = "east", 54 | velocity = "160", 55 | barrelLength = "8", 56 | forecastMov = "24", 57 | forecastRot = "3.4", 58 | gravity = "0.05", 59 | drag = "0.01", 60 | P = "1", 61 | D = "6" 62 | } 63 | end 64 | 65 | system.updatePersistentData = function() 66 | system.write(system.fileName, properties) 67 | end 68 | 69 | system.write = function(file, obj) 70 | system.file = io.open(file, "w") 71 | system.file:write(textutils.serialise(obj)) 72 | system.file:close() 73 | end 74 | 75 | system.init() 76 | 77 | local gear = peripheral.find("Create_RotationSpeedController") 78 | 79 | if not gear then 80 | printError("Need SpeedController") 81 | else 82 | gear.setTargetSpeed(0) 83 | for i = 1, 2, 1 do 84 | redstone.setOutput(properties.power_on, false) 85 | redstone.setOutput(properties.power_on, true) 86 | end 87 | sleep(0.5) 88 | end 89 | 90 | local cannon = peripheral.find("cbc_cannon_mount") 91 | if not cannon then 92 | printError("Need peripheral: cbc_cannon_mount") 93 | return 94 | end 95 | 96 | -----------function------------ 97 | local quatMultiply = function(q1, q2) 98 | local newQuat = {} 99 | newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w 100 | newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x 101 | newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y 102 | newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z 103 | return newQuat 104 | end 105 | 106 | local RotateVectorByQuat = function(quat, v) 107 | local x = quat.x * 2 108 | local y = quat.y * 2 109 | local z = quat.z * 2 110 | local xx = quat.x * x 111 | local yy = quat.y * y 112 | local zz = quat.z * z 113 | local xy = quat.x * y 114 | local xz = quat.x * z 115 | local yz = quat.y * z 116 | local wx = quat.w * x 117 | local wy = quat.w * y 118 | local wz = quat.w * z 119 | local res = {} 120 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 121 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 122 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 123 | return res 124 | end 125 | 126 | local negaQ = function(q) 127 | return { 128 | w = q.w, 129 | x = -q.x, 130 | y = -q.y, 131 | z = -q.z 132 | } 133 | end 134 | 135 | local vector = {} 136 | local newVec = function (x, y, z) 137 | if type(x) == "table" then 138 | return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector }) 139 | elseif x and y and z then 140 | return setmetatable({ x = x, y = y, z = z}, { __index = vector}) 141 | else 142 | return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector}) 143 | end 144 | end 145 | 146 | function vector:zero() 147 | self.x = 0 148 | self.y = 0 149 | self.z = 0 150 | return self 151 | end 152 | 153 | function vector:copy() 154 | return newVec(self.x, self.y, self.z) 155 | end 156 | 157 | function vector:len() 158 | return math.sqrt(self.x ^ 2 + self.y ^ 2 + self.z ^ 2) 159 | end 160 | 161 | function vector:norm() 162 | local l = self:len() 163 | if l == 0 then 164 | self:zero() 165 | else 166 | self.x = self.x / l 167 | self.y = self.y / l 168 | self.z = self.z / l 169 | end 170 | return self 171 | end 172 | 173 | function vector:nega() 174 | self.x = -self.x 175 | self.y = -self.y 176 | self.z = -self.z 177 | return self 178 | end 179 | 180 | function vector:add(v) 181 | self.x = self.x + v.x 182 | self.y = self.y + v.y 183 | self.z = self.z + v.z 184 | return self 185 | end 186 | 187 | function vector:sub(v) 188 | self.x = self.x - v.x 189 | self.y = self.y - v.y 190 | self.z = self.z - v.z 191 | return self 192 | end 193 | 194 | function vector:scale(num) 195 | self.x = self.x * num 196 | self.y = self.y * num 197 | self.z = self.z * num 198 | return self 199 | end 200 | 201 | function vector:unpack() 202 | return self.x, self.y, self.z 203 | end 204 | 205 | local unpackVec = function(v) 206 | return v.x, v.y, v.z 207 | end 208 | 209 | local matrixMultiplication_3d = function (m, v) 210 | return { 211 | x = m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z, 212 | y = m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z, 213 | z = m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z 214 | } 215 | end 216 | 217 | local newQuat = function () 218 | return { 219 | w = 1, 220 | x = 0, 221 | y = 0, 222 | z = 0 223 | } 224 | end 225 | 226 | local quatList = { 227 | west = { 228 | w = -1, 229 | x = 0, 230 | y = 0, 231 | z = 0 232 | }, 233 | south = { 234 | w = -0.70710678118654752440084436210485, 235 | x = 0, 236 | y = -0.70710678118654752440084436210485, 237 | z = 0 238 | }, 239 | east = { 240 | w = 0, 241 | x = 0, 242 | y = -1, 243 | z = 0 244 | }, 245 | north = { 246 | w = -0.70710678118654752440084436210485, 247 | x = 0, 248 | y = 0.70710678118654752440084436210485, 249 | z = 0 250 | } 251 | } 252 | 253 | local copysign = function(num1, num2) 254 | num1 = math.abs(num1) 255 | num1 = num2 > 0 and num1 or -num1 256 | return num1 257 | end 258 | 259 | local genParticle = function(x, y, z) 260 | commands.execAsync(string.format("particle electric_spark %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z)) 261 | end 262 | 263 | local genStr = function(s, count) 264 | local result = "" 265 | for i = 1, count, 1 do 266 | result = result .. s 267 | end 268 | return result 269 | end 270 | 271 | local resetAngelRange = function(angle) 272 | if (math.abs(angle) > 180) then 273 | angle = math.abs(angle) >= 360 and angle % 360 or angle 274 | return -copysign(360 - math.abs(angle), angle) 275 | else 276 | return angle 277 | end 278 | end 279 | 280 | local rayCaster = { 281 | block = nil 282 | } 283 | 284 | rayCaster.run = function(start, v3Speed, range, showParticle) 285 | local vec = {} 286 | vec.x = start.x 287 | vec.y = start.y 288 | vec.z = start.z 289 | for i = 0, range, 1 do 290 | vec.x = vec.x + v3Speed.x 291 | vec.y = vec.y + v3Speed.y 292 | vec.z = vec.z + v3Speed.z 293 | 294 | rayCaster.block = coordinate.getBlock(vec.x, vec.y, vec.z - 1) 295 | if (rayCaster.block ~= "minecraft:air" or i >= range) then 296 | break 297 | end 298 | if showParticle then 299 | genParticle(vec.x, vec.y, vec.z) 300 | end 301 | end 302 | return { 303 | x = vec.x, 304 | y = vec.y, 305 | z = vec.z, 306 | name = rayCaster.block 307 | } 308 | end 309 | 310 | local getCannonPos = function() 311 | local wPos = ship.getWorldspacePosition() 312 | local yardPos = ship.getShipyardPosition() 313 | local selfPos = coordinate.getAbsoluteCoordinates() 314 | local offset = { 315 | x = yardPos.x - selfPos.x - 0.5 - properties.cannonOffset.x, 316 | y = yardPos.y - selfPos.y - 0.5 - properties.cannonOffset.y, 317 | z = yardPos.z - selfPos.z - 0.5 - properties.cannonOffset.z 318 | } 319 | offset = RotateVectorByQuat(ship.getQuaternion(), offset) 320 | return { 321 | x = wPos.x - offset.x, 322 | y = wPos.y - offset.y, 323 | z = wPos.z - offset.z 324 | } 325 | end 326 | 327 | function math.lerp(a, b, t) 328 | return a + (b - a) * t 329 | end 330 | 331 | local pdCt = function(tgYaw, omega, p, d) 332 | local result = tgYaw * p + omega * d 333 | return math.abs(result) > properties.max_rotate_speed and copysign(properties.max_rotate_speed, result) or result 334 | end 335 | 336 | local ln = function(x) 337 | return math.log(x) / math.log(math.exp(1)) 338 | end 339 | 340 | local getTime = function(dis, pitch) 341 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 342 | barrelLength = barrelLength and barrelLength or 0 343 | local cosP = math.abs(math.cos(pitch)) 344 | dis = dis - barrelLength * cosP 345 | 346 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 347 | v0 = v0 and v0 or 0 348 | 349 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 350 | drag = drag and 1 - drag or 0.99 351 | 352 | local result 353 | 354 | if drag < 0.001 or drag > 0.999 then 355 | result = dis / (cosP * v0) 356 | else 357 | result = math.abs(math.log(1 - dis / (100 * (cosP * v0))) / ln(drag)) 358 | end 359 | -- local result = math.log((dis * lnD) / (v0 * cosP) + 1, drag) 360 | 361 | return result and result or 0 362 | end 363 | 364 | local getY2 = function(t, y0, pitch) 365 | if t > 10000 then 366 | return 0 367 | end 368 | local grav = #properties.gravity == 0 and 0 or tonumber(properties.gravity) 369 | grav = grav and grav or 0.05 370 | local sinP = math.sin(pitch) 371 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 372 | barrelLength = barrelLength and barrelLength or 0 373 | y0 = barrelLength * sinP + y0 374 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 375 | v0 = v0 and v0 or 0 376 | local Vy = v0 * sinP 377 | 378 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 379 | drag = drag and 1 - drag or 0.99 380 | if drag < 0.001 then 381 | drag = 1 382 | end 383 | local index = 1 384 | local last = 0 385 | while index < t + 1 do 386 | y0 = y0 + Vy 387 | Vy = drag * Vy - grav 388 | if index == math.floor(t) then 389 | last = y0 390 | end 391 | index = index + 1 392 | end 393 | 394 | return math.lerp(last, y0, t % math.floor(t)) 395 | end 396 | 397 | local ag_binary_search = function(arr, xDis, y0, yDis) 398 | local low = 1 399 | local high = #arr 400 | local mid, time 401 | local pitch, result = 0, 0 402 | while low <= high do 403 | mid = math.floor((low + high) / 2) 404 | pitch = arr[mid] 405 | time = getTime(xDis, pitch) 406 | result = yDis - getY2(time, y0, pitch) 407 | if result >= -0.018 and result <= 0.018 then 408 | break 409 | --return mid, time 410 | elseif result > 0 then 411 | low = mid + 1 412 | else 413 | high = mid - 1 414 | end 415 | end 416 | return pitch, time 417 | end 418 | 419 | local vector_scale = function (v, s) 420 | return { 421 | x = v.x * s, 422 | y = v.y * s, 423 | z = v.z * s 424 | } 425 | end 426 | 427 | local parent = { 428 | pos = newVec(), 429 | quat = newQuat(), 430 | omega = newVec(), 431 | velocity = newVec() 432 | } 433 | local controlCenter = { 434 | tgPos = newVec(), 435 | velocity = newVec(), 436 | mode = 2, 437 | fire = false 438 | } 439 | local ct = 20 440 | local listener = function() 441 | local parentId = #properties.phyBearID == 0 and 0 or tonumber(properties.phyBearID) 442 | parentId = parentId and parentId or 0 443 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 444 | controlCenterId = controlCenterId and controlCenterId or 0 445 | while true do 446 | local id, msg = rednet.receive(protocol, 2) 447 | if not id then 448 | parentId = #properties.phyBearID == 0 and 0 or tonumber(properties.phyBearID) 449 | parentId = parentId and parentId or 0 450 | elseif id == parentId then 451 | parent.quat = msg.quat 452 | parent.omega = msg.omega 453 | parent.slug = msg.slug 454 | parent.velocity = msg.velocity 455 | parent.pos = msg.pos 456 | elseif id == controlCenterId then 457 | controlCenter = msg 458 | ct = 20 459 | end 460 | end 461 | end 462 | 463 | local cannonPos = newVec() 464 | local cross_point = newVec() 465 | local box_1 = peripheral.find("gtceu:lv_super_chest") 466 | box_1 = box_1 and box_1 or peripheral.find("gtceu:mv_super_chest") 467 | box_1 = box_1 and box_1 or peripheral.find("gtceu:hv_super_chest") 468 | box_1 = box_1 and box_1 or peripheral.find("gtceu:ev_super_chest") 469 | local box_2 = peripheral.find("create:depot") 470 | local bullets_count = 0 471 | local getBullets_count = function () 472 | while true do 473 | local box_1_count, box_2_count = 0, 0 474 | if box_2 and box_2.getItemDetail(1) then 475 | box_2_count = box_2.getItemDetail(1).count 476 | end 477 | if box_1 and box_1.getItemDetail(1) then 478 | box_1_count = box_1.getItemDetail(1).count 479 | end 480 | bullets_count = box_1_count + box_2_count 481 | if not box_1 and not box_2 then 482 | sleep(0.05) 483 | end 484 | end 485 | end 486 | 487 | local sendRequest = function() 488 | local slug = ship and ship.getName() or nil 489 | while true do 490 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 491 | controlCenterId = controlCenterId and controlCenterId or 0 492 | rednet.send(controlCenterId, { 493 | name = properties.cannonName, 494 | pw = properties.password, 495 | slug = slug, 496 | yawSlug = parent.slug, 497 | bullets_count = bullets_count, 498 | cross_point = cross_point 499 | }, request_protocol) 500 | sleep(0.05) 501 | end 502 | end 503 | 504 | local run_msgSend = function () 505 | parallel.waitForAll(sendRequest, getBullets_count) 506 | end 507 | 508 | local runListener = function() 509 | parallel.waitForAll(run_msgSend, listener) 510 | end 511 | 512 | local cannonUtil = { 513 | pos = newVec(), 514 | prePos = newVec(), 515 | velocity = newVec() 516 | } 517 | 518 | local gatMatrixFromFaceVec = function (v) 519 | return { 520 | {v.x, 0, v.z}, 521 | {0, 1, 0}, 522 | {-v.z, 0, v.x}, 523 | } 524 | end 525 | 526 | local getVecFromFace = function (face) 527 | if face == "west" then 528 | return newVec(-1, 0, 0) 529 | elseif face == "east" then 530 | return newVec(1, 0, 0) 531 | elseif face == "north" then 532 | return newVec(0, 0, -1) 533 | elseif face == "south" then 534 | return newVec(0, 0, 1) 535 | end 536 | end 537 | 538 | function cannonUtil:getAtt() 539 | self.pos = getCannonPos() 540 | if ship then 541 | local v = ship.getVelocity() 542 | self.velocity = { 543 | x = v.x / 20, 544 | y = v.y / 20, 545 | z = v.z / 20 546 | } 547 | else 548 | self.velocity = { 549 | x = self.pos.x - self.prePos.x, 550 | y = self.pos.y - self.prePos.y, 551 | z = self.pos.z - self.prePos.z 552 | } 553 | end 554 | 555 | self.matrix = gatMatrixFromFaceVec(getVecFromFace(properties.face)) 556 | end 557 | 558 | function cannonUtil:setPreAtt() 559 | self.prePos = self.pos 560 | end 561 | 562 | function cannonUtil:getNextPos(t) 563 | return { 564 | x = self.pos.x + self.velocity.x * t, 565 | y = self.pos.y + self.velocity.y * t, 566 | z = self.pos.z + self.velocity.z * t 567 | } 568 | end 569 | 570 | local pitchList = {} 571 | for i = -90, 90, 0.0375 do 572 | table.insert(pitchList, math.rad(i)) 573 | end 574 | 575 | ------------------------------------------ 576 | 577 | local omega2Q = function (omega, tick) 578 | local omegaRot = { 579 | x = omega.x / tick, 580 | y = omega.y / tick, 581 | z = omega.z / tick 582 | } 583 | local sqrt = math.sqrt(omegaRot.x ^ 2 + omegaRot.y ^ 2 + omegaRot.z ^ 2) 584 | sqrt = math.abs(sqrt) > math.pi and copysign(math.pi, sqrt) or sqrt 585 | if sqrt ~= 0 then 586 | omegaRot.x = omegaRot.x / sqrt 587 | omegaRot.y = omegaRot.y / sqrt 588 | omegaRot.z = omegaRot.z / sqrt 589 | local halfTheta = sqrt / 2 590 | local sinHTheta = math.sin(halfTheta) 591 | return { 592 | w = math.cos(halfTheta), 593 | x = omegaRot.x * sinHTheta, 594 | y = omegaRot.y * sinHTheta, 595 | z = omegaRot.z * sinHTheta 596 | } 597 | else 598 | return nil 599 | end 600 | end 601 | 602 | local matrixMultiplication = function(m, v) 603 | return { x = m[1][1] * v.x + m[1][2] * v.y, y = m[2][1] * v.x + m[2][2] * v.y} 604 | end 605 | local sin2cos = function(s) 606 | return math.sqrt( 1 - s ^ 2) 607 | end 608 | local rotMatrix_90 = { 609 | {0, -1}, {1, 0} 610 | } 611 | local fire = false 612 | local runCt = function() 613 | while true do 614 | fire = controlCenter.fire 615 | local tgPitch = 0 616 | cannonUtil:getAtt() 617 | 618 | local omega = RotateVectorByQuat(negaQ(ship.getQuaternion()), ship.getOmega()) 619 | 620 | local nextQ, pNextQ = ship.getQuaternion(), parent.quat 621 | --commands.execAsync(("say x=%0.2f, y=%0.2f, z=%0.2f"):format(pOmega.x, pOmega.y, pOmega.z)) 622 | 623 | local forecastRot = #properties.forecastRot == 0 and 0 or tonumber(properties.forecastRot) 624 | forecastRot = forecastRot and forecastRot or 16 625 | 626 | local omegaQuat = omega2Q(parent.omega, 20 / forecastRot) 627 | 628 | if omegaQuat then 629 | nextQ = quatMultiply(nextQ, omegaQuat) 630 | pNextQ = quatMultiply(pNextQ, omegaQuat) 631 | end 632 | 633 | local pErr = { 634 | x = cannonUtil.pos.x - parent.pos.x, 635 | y = cannonUtil.pos.y - parent.pos.y, 636 | z = cannonUtil.pos.z - parent.pos.z, 637 | } 638 | 639 | local pNextQ2 = parent.quat 640 | local omegaQ2 = omega2Q(parent.omega, 6 / forecastRot) 641 | if omegaQ2 then 642 | pNextQ2 = quatMultiply(pNextQ2, omegaQ2) 643 | end 644 | pErr = RotateVectorByQuat(negaQ(parent.quat), pErr) 645 | pErr = RotateVectorByQuat(pNextQ2, pErr) 646 | 647 | --commands.execAsync(("say x=%0.4f, y=%0.4f, z=%0.4f"):format(pErr.x, pErr.y, pErr.z)) 648 | 649 | local forecastMov = #properties.forecastMov == 0 and 0 or tonumber(properties.forecastMov) 650 | forecastMov = forecastMov and forecastMov or 16 651 | cannonPos = { 652 | x = parent.pos.x + parent.velocity.x * forecastMov, 653 | y = parent.pos.y + parent.velocity.y * forecastMov, 654 | z = parent.pos.z + parent.velocity.z * forecastMov 655 | } 656 | cannonPos.x = cannonPos.x + pErr.x 657 | cannonPos.y = cannonPos.y + pErr.y 658 | cannonPos.z = cannonPos.z + pErr.z 659 | 660 | if commands then 661 | genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 662 | end 663 | 664 | local cannonPitch = cannon.getPitch() 665 | if ct > 0 then 666 | ct = ct - 1 667 | local target = controlCenter.tgPos 668 | target.y = target.y + 0.5 669 | local tgVec = { 670 | x = target.x - cannonPos.x, 671 | y = target.y - cannonPos.y, 672 | z = target.z - cannonPos.z 673 | } 674 | local tmpT = (500 - math.sqrt(tgVec.x ^ 2 + tgVec.y ^ 2 + tgVec.z ^ 2)) / 500 675 | tmpT = tmpT < 0 and 0 or tmpT * 8 676 | 677 | target.x = target.x + controlCenter.velocity.x * tmpT 678 | target.y = target.y + controlCenter.velocity.y * tmpT 679 | target.z = target.z + controlCenter.velocity.z * tmpT 680 | 681 | tgVec = { 682 | x = target.x - cannonPos.x, 683 | y = target.y - cannonPos.y, 684 | z = target.z - cannonPos.z 685 | } 686 | --genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 687 | 688 | local xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 689 | local tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 690 | local tmpVec 691 | 692 | if cTime > 5 then 693 | if controlCenter.mode > 2 then 694 | target.x = target.x + controlCenter.velocity.x * cTime 695 | target.y = target.y + controlCenter.velocity.y * cTime 696 | target.z = target.z + controlCenter.velocity.z * cTime 697 | tgVec = { 698 | x = target.x - cannonPos.x, 699 | y = target.y - cannonPos.y, 700 | z = target.z - cannonPos.z 701 | } 702 | xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 703 | tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 704 | end 705 | 706 | local _c = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 707 | local allDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2 + tgVec.z ^ 2) 708 | local cosP = math.cos(tmpPitch) 709 | tmpVec = { 710 | x = allDis * (tgVec.x / _c) * cosP, 711 | y = allDis * math.sin(tmpPitch), 712 | z = allDis * (tgVec.z / _c) * cosP 713 | } 714 | else 715 | tmpVec = tgVec 716 | end 717 | 718 | local rot = RotateVectorByQuat(negaQ(pNextQ), tmpVec) 719 | rot = matrixMultiplication_3d(cannonUtil.matrix, rot) 720 | local rotY = RotateVectorByQuat(negaQ(nextQ), tmpVec) 721 | rotY = matrixMultiplication_3d(cannonUtil.matrix, rotY) 722 | 723 | local tmpYaw = -math.atan2(rotY.z, -rotY.x) 724 | local localVec = RotateVectorByQuat(quatMultiply(quatList[properties.lock_yaw_face], 725 | negaQ(parent.quat)), tmpVec) 726 | 727 | local yaw_range = #properties.lock_yaw_range == 0 and 0 or tonumber(properties.lock_yaw_range) 728 | yaw_range = yaw_range and yaw_range or 0 729 | local localYaw = -math.deg(math.atan2(localVec.z, -localVec.x)) 730 | if math.abs(localYaw) < yaw_range then 731 | tmpYaw = 0 732 | fire = false 733 | end 734 | 735 | local p = #properties.P == 0 and 0 or tonumber(properties.P) 736 | p = p and p or 0 737 | local d = #properties.D == 0 and 0 or tonumber(properties.D) 738 | d = d and d or 0 739 | local yawSpeed = math.floor(pdCt(math.deg(tmpYaw), omega.y, p, d) + 0.5) 740 | if properties.InvertYaw then 741 | yawSpeed = -yawSpeed 742 | end 743 | 744 | local id = #properties.phyBearID == 0 and 0 or tonumber(properties.phyBearID) 745 | id = id and id or 0 746 | rednet.send(id, yawSpeed, protocol) 747 | 748 | ------self(pitch)------- 749 | tgPitch = math.deg(math.asin(rot.y / math.sqrt(rot.x ^ 2 + rot.y ^ 2 + rot.z ^ 2))) 750 | local tgDis = math.sqrt(tmpVec.x ^ 2 + tmpVec.y ^ 2 + tmpVec.z ^ 2) 751 | local tgPoint = vector_scale(getVecFromFace(properties.face), -tgDis) 752 | 753 | local w_axis = { 754 | x = math.sqrt(tgPoint.x ^ 2 + tgPoint.z ^ 2), 755 | y = tgPoint.y 756 | } 757 | 758 | local p_angle = -math.rad(cannonPitch) 759 | local m = { 760 | {math.cos(p_angle), math.sin(p_angle)}, 761 | {-math.sin(p_angle), math.cos(p_angle)} 762 | } 763 | w_axis = matrixMultiplication(m, w_axis) 764 | local y_a = math.atan2(tgPoint.x, tgPoint.z) 765 | tgPoint.y = w_axis.y 766 | tgPoint.x = math.sin(y_a) * w_axis.x 767 | tgPoint.z = math.cos(y_a) * w_axis.x 768 | if properties.InvertYaw then 769 | tgPoint.x = tgPoint.x 770 | tgPoint.z = tgPoint.z 771 | end 772 | 773 | cross_point = RotateVectorByQuat(ship.getQuaternion(), tgPoint) 774 | 775 | cross_point.x = cannonUtil.pos.x + cross_point.x 776 | cross_point.y = cannonUtil.pos.y + cross_point.y 777 | cross_point.z = cannonUtil.pos.z + cross_point.z 778 | else 779 | fire = false 780 | local point = getVecFromFace(properties.idleFace) 781 | 782 | local xP = RotateVectorByQuat(parent.quat, point) 783 | local pq = negaQ(ship.getQuaternion()) 784 | 785 | local xP2 = RotateVectorByQuat(pq, xP) 786 | 787 | local resultYaw = -math.deg(math.atan2(xP2.x, xP2.z)) 788 | if properties.InvertYaw then 789 | resultYaw = -resultYaw 790 | end 791 | 792 | local yawSpeed = math.floor(pdCt(resultYaw, omega.y, 1, 2) + 0.5) 793 | 794 | local id = #properties.phyBearID == 0 and 0 or tonumber(properties.phyBearID) 795 | id = id and id or 0 796 | 797 | rednet.send(id, yawSpeed, protocol) 798 | 799 | cross_point = nil 800 | end 801 | 802 | if tgPitch < properties.minPitchAngle then 803 | tgPitch = properties.minPitchAngle 804 | fire = false 805 | end 806 | 807 | if properties.InvertPitch then 808 | tgPitch = resetAngelRange(tgPitch - cannonPitch) 809 | else 810 | tgPitch = resetAngelRange(cannonPitch - tgPitch) 811 | end 812 | 813 | if math.abs(tgPitch) > 30 then 814 | fire = false 815 | end 816 | 817 | tgPitch = math.abs(tgPitch) >= 0.01875 and tgPitch or 0 818 | local pSpeed = math.floor(tgPitch * ANGLE_TO_SPEED / 2 + 0.5) 819 | pSpeed = math.abs(pSpeed) < properties.max_rotate_speed and pSpeed or 820 | copysign(properties.max_rotate_speed, pSpeed) 821 | 822 | gear.setTargetSpeed(pSpeed) 823 | cannonUtil:setPreAtt() 824 | end 825 | end 826 | 827 | local termUtil = { 828 | cpX = 1, 829 | cpY = 1 830 | } 831 | 832 | local absTextField = { 833 | x = 1, 834 | y = 1, 835 | len = 15, 836 | text = "", 837 | textCorlor = "0", 838 | backgroundColor = "8" 839 | } 840 | 841 | function absTextField:paint() 842 | local str = "" 843 | for i = 1, self.len, 1 do 844 | local text = tostring(self.key[self.value]) 845 | local tmp = string.sub(text, i, i) 846 | if #tmp > 0 then 847 | str = str .. tmp 848 | else 849 | local tmp2 = "" 850 | for j = 0, self.len - i, 1 do 851 | tmp2 = tmp2 .. " " 852 | end 853 | str = str .. tmp2 854 | break 855 | end 856 | end 857 | 858 | term.setCursorPos(self.x, self.y) 859 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 860 | end 861 | 862 | function absTextField:inputChar(char) 863 | local xPos, yPos = term.getCursorPos() 864 | xPos = xPos + 1 - self.x 865 | local field = tostring(self.key[self.value]) 866 | if #field < self.len then 867 | if self.type == "number" then 868 | if char >= '0' and char <= '9' then 869 | if field == "0" then 870 | field = char 871 | else 872 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 873 | end 874 | 875 | self.key[self.value] = tonumber(field) 876 | termUtil.cpX = termUtil.cpX + 1 877 | end 878 | if char == '-' then 879 | self.key[self.value] = -self.key[self.value] 880 | end 881 | elseif self.type == "string" then 882 | local strEnd = string.sub(field, xPos, #field) 883 | field = string.sub(field, 1, xPos - 1) .. char .. strEnd 884 | self.key[self.value] = field 885 | termUtil.cpX = termUtil.cpX + 1 886 | end 887 | end 888 | end 889 | 890 | function absTextField:inputKey(key) 891 | local xPos, yPos = term.getCursorPos() 892 | local field = tostring(self.key[self.value]) 893 | local minXp = self.x 894 | local maxXp = minXp + #field 895 | if key == 259 or key == 261 then -- backSpace 896 | if xPos > minXp then 897 | termUtil.cpX = termUtil.cpX - 1 898 | if #field > 0 and termUtil.cpX > 1 then 899 | local index = termUtil.cpX - self.x 900 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 901 | end 902 | if self.type == "number" then 903 | local number = tonumber(field) 904 | if not number then 905 | self.key[self.value] = 0 906 | else 907 | self.key[self.value] = number 908 | end 909 | elseif self.type == "string" then 910 | self.key[self.value] = field 911 | end 912 | end 913 | elseif key == 257 or key == 335 then 914 | -- print("enter") 915 | elseif key == 262 or key == 263 then 916 | if key == 262 then 917 | termUtil.cpX = termUtil.cpX + 1 918 | elseif key == 263 then 919 | termUtil.cpX = termUtil.cpX - 1 920 | end 921 | elseif key == 264 or key == 258 then 922 | -- print("down") 923 | elseif key == 265 then 924 | -- print("up") 925 | end 926 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 927 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 928 | end 929 | 930 | function absTextField:click(x, y) 931 | local xPos = self.x 932 | if x >= xPos then 933 | if x < xPos + #tostring(self.key[self.value]) then 934 | termUtil.cpX, termUtil.cpY = x, y 935 | else 936 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 937 | end 938 | end 939 | end 940 | 941 | local newTextField = function(key, value, x, y) 942 | return setmetatable({ 943 | key = key, 944 | value = value, 945 | type = type(key[value]), 946 | x = x, 947 | y = y 948 | }, { 949 | __index = absTextField 950 | }) 951 | end 952 | 953 | local absSelectBox = { 954 | x = 1, 955 | y = 1, 956 | label = "", 957 | contents = {}, 958 | count = 0, 959 | interval = 0, 960 | fontColor = "8", 961 | backgroundColor = "f", 962 | selectColor = "e" 963 | } 964 | 965 | function absSelectBox:paint() 966 | term.setCursorPos(self.x, self.y) 967 | local select = tostring(self.key[self.value]) 968 | for i = 1, #self.contents, 1 do 969 | local str = tostring(self.contents[i]) 970 | if select == str then 971 | term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str)) 972 | else 973 | term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str)) 974 | end 975 | for j = 1, self.interval, 1 do 976 | term.write(" ") 977 | end 978 | end 979 | end 980 | 981 | function absSelectBox:click(x, y) 982 | local xPos = x + 1 - self.x 983 | local index = 0 984 | for i = 1, #self.contents, 1 do 985 | if xPos >= index and xPos <= index + #tostring(self.contents[i]) then 986 | self.key[self.value] = self.contents[i] 987 | break 988 | end 989 | index = index + #tostring(self.contents[i]) + self.interval 990 | end 991 | end 992 | 993 | local newSelectBox = function(key, value, interval, x, y, ...) 994 | return setmetatable({ 995 | key = key, 996 | value = value, 997 | interval = interval, 998 | x = x, 999 | y = y, 1000 | type = type(key[value]), 1001 | contents = {...} 1002 | }, { 1003 | __index = absSelectBox 1004 | }) 1005 | end 1006 | 1007 | local absSlider = { 1008 | x = 1, 1009 | y = 1, 1010 | min = 0, 1011 | max = 1, 1012 | len = 20, 1013 | fontColor = "8", 1014 | backgroundColor = "f" 1015 | } 1016 | 1017 | function absSlider:paint() 1018 | local field = self.key[self.value] 1019 | if field == "-" then 1020 | field = 0 1021 | end 1022 | local maxVal = self.max - self.min 1023 | local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5) 1024 | xPos = xPos < 1 and 1 or xPos 1025 | term.setCursorPos(self.x, self.y) 1026 | for i = 1, self.len, 1 do 1027 | if xPos == i then 1028 | term.blit(" ", self.backgroundColor, self.fontColor) 1029 | else 1030 | term.blit("-", self.fontColor, self.backgroundColor) 1031 | end 1032 | end 1033 | end 1034 | 1035 | function absSlider:click(x, y) 1036 | local xPos = x + 1 - self.x 1037 | if xPos > self.len then 1038 | xPos = self.len 1039 | end 1040 | self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min 1041 | end 1042 | 1043 | local newSlider = function(key, value, min, max, len, x, y) 1044 | return setmetatable({ 1045 | key = key, 1046 | value = value, 1047 | min = min, 1048 | max = max, 1049 | len = len, 1050 | x = x, 1051 | y = y 1052 | }, { 1053 | __index = absSlider 1054 | }) 1055 | end 1056 | 1057 | local selfId = os.getComputerID() 1058 | local runTerm = function() 1059 | local fieldTb = { 1060 | velocity = newTextField(properties, "velocity", 11, 2), 1061 | barrelLength = newTextField(properties, "barrelLength", 30, 2), 1062 | phyBearIdField = newTextField(properties, "phyBearID", 46, 2), 1063 | minPitchAngle = newTextField(properties, "minPitchAngle", 17, 8), 1064 | max_rotate_speed = newTextField(properties, "max_rotate_speed", 20, 10), 1065 | lock_yaw_range = newTextField(properties, "lock_yaw_range", 20, 12), 1066 | cannonOffset_x = newTextField(properties.cannonOffset, "x", 18, 7), 1067 | cannonOffset_y = newTextField(properties.cannonOffset, "y", 24, 7), 1068 | cannonOffset_z = newTextField(properties.cannonOffset, "z", 30, 7), 1069 | P = newTextField(properties, "P", 4, 16), 1070 | D = newTextField(properties, "D", 12, 16), 1071 | gravity = newTextField(properties, "gravity", 45, 6), 1072 | drag = newTextField(properties, "drag", 45, 7), 1073 | forecastMov = newTextField(properties, "forecastMov", 46, 16), 1074 | forecastRot = newTextField(properties, "forecastRot", 46, 17), 1075 | cannonName = newTextField(properties, "cannonName", 14, 17), 1076 | controlCenterId = newTextField(properties, "controlCenterId", 19, 18), 1077 | password = newTextField(properties, "password", 37, 18) 1078 | } 1079 | fieldTb.velocity.len = 5 1080 | fieldTb.barrelLength.len = 3 1081 | fieldTb.controlCenterId.len = 5 1082 | fieldTb.password.len = 14 1083 | fieldTb.minPitchAngle.len = 5 1084 | fieldTb.max_rotate_speed.len = 5 1085 | fieldTb.lock_yaw_range.len = 5 1086 | fieldTb.phyBearIdField.len = 5 1087 | fieldTb.cannonOffset_x.len = 3 1088 | fieldTb.cannonOffset_y.len = 3 1089 | fieldTb.cannonOffset_z.len = 3 1090 | fieldTb.P.len = 5 1091 | fieldTb.D.len = 5 1092 | fieldTb.gravity.len = 6 1093 | fieldTb.drag.len = 6 1094 | fieldTb.forecastMov.len = 5 1095 | fieldTb.forecastRot.len = 5 1096 | local selectBoxTb = { 1097 | power_on = newSelectBox(properties, "power_on", 2, 12, 3, "top", "left", "right", "front", "back"), 1098 | fire = newSelectBox(properties, "fire", 2, 8, 4, "top", "left", "right", "front", "back"), 1099 | face = newSelectBox(properties, "face", 2, 8, 5, "south", "west", "north", "east"), 1100 | idleFace = newSelectBox(properties, "idleFace", 1, 14, 6, "south", "west", "north", "east"), 1101 | lock_yaw_face = newSelectBox(properties, "lock_yaw_face", 2, 27, 12, "south", "west", "north", "east"), 1102 | InvertYaw = newSelectBox(properties, "InvertYaw", 1, 41, 14, false, true), 1103 | InvertPitch = newSelectBox(properties, "InvertPitch", 1, 15, 14, false, true) 1104 | } 1105 | 1106 | local sliderTb = { 1107 | minPitchAngle = newSlider(properties, "minPitchAngle", -45, 60, 49, 2, 9), 1108 | max_rotate_speed = newSlider(properties, "max_rotate_speed", 0, 256, 49, 2, 11) 1109 | } 1110 | 1111 | local alarm_id = os.setAlarm(os.time() + 0.05) 1112 | local alarm_flag = false 1113 | term.clear() 1114 | term.setCursorPos(15, 8) 1115 | term.write("Press any key to continue") 1116 | while true do 1117 | local eventData = {os.pullEvent()} 1118 | local event = eventData[1] 1119 | if event == "mouse_up" or event == "key_up" or event == "alarm" or event == "mouse_click" or event == 1120 | "mouse_drag" or event == "key" or event == "char" then 1121 | if not alarm_flag then 1122 | alarm_flag = true 1123 | else 1124 | term.clear() 1125 | term.setCursorPos(18, 1) 1126 | printError(string.format("self id: %d", selfId)) 1127 | term.setCursorPos(2, 2) 1128 | term.write("Velocity") 1129 | term.setCursorPos(17, 2) 1130 | term.write("BarrelLength") 1131 | term.setCursorPos(36, 2) 1132 | term.write("Yaw_Pc_ID") 1133 | 1134 | term.setCursorPos(2, 3) 1135 | term.write("POWER_ON: ") 1136 | term.setCursorPos(2, 4) 1137 | term.write("FIRE: ") 1138 | 1139 | term.setCursorPos(2, 7) 1140 | term.write("CannonOffset: x= y= z=") 1141 | term.setCursorPos(36, 6) 1142 | term.write("gravity: ") 1143 | term.setCursorPos(36, 7) 1144 | term.write(" drag: ") 1145 | 1146 | term.setCursorPos(2, 8) 1147 | term.write("MinPitchAngle: ") 1148 | term.setCursorPos(2, 10) 1149 | term.write("Max_rotate_speed: ") 1150 | term.setCursorPos(2, 12) 1151 | term.write("lock_yaw_range: +-") 1152 | 1153 | term.setCursorPos(2, 5) 1154 | term.write("Face: ") 1155 | term.setCursorPos(2, 6) 1156 | term.write("idleFace: ") 1157 | 1158 | term.setCursorPos(2, 14) 1159 | term.write("InvertPitch: ") 1160 | term.setCursorPos(30, 14) 1161 | term.write("InvertYaw: ") 1162 | 1163 | term.setCursorPos(2, 16) 1164 | term.write("P: ") 1165 | term.setCursorPos(10, 16) 1166 | term.write("D: ") 1167 | term.setCursorPos(34, 16) 1168 | term.write("forecastMov: ") 1169 | term.setCursorPos(34, 17) 1170 | term.write("forecastRot: ") 1171 | 1172 | term.setCursorPos(2, 17) 1173 | term.write("cannonName: ") 1174 | term.setCursorPos(2, 18) 1175 | term.write("controlCenterId: ") 1176 | term.setCursorPos(27, 18) 1177 | term.write("Password: ") 1178 | 1179 | for k, v in pairs(fieldTb) do 1180 | v:paint() 1181 | end 1182 | 1183 | for k, v in pairs(selectBoxTb) do 1184 | v:paint() 1185 | end 1186 | 1187 | for k, v in pairs(sliderTb) do 1188 | v:paint() 1189 | end 1190 | 1191 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 1192 | 1193 | if event == "mouse_click" then 1194 | term.setCursorBlink(true) 1195 | local x, y = eventData[3], eventData[4] 1196 | for k, v in pairs(fieldTb) do -- 点击了输入框 1197 | if y == v.y and x >= v.x and x <= v.x + v.len then 1198 | v:click(x, y) 1199 | end 1200 | end 1201 | for k, v in pairs(selectBoxTb) do -- 点击了选择框 1202 | if y == v.y then 1203 | v:click(x, y) 1204 | end 1205 | end 1206 | for k, v in pairs(sliderTb) do 1207 | if y == v.y then 1208 | v:click(x, y) 1209 | end 1210 | end 1211 | elseif event == "mouse_drag" then 1212 | local x, y = eventData[3], eventData[4] 1213 | for k, v in pairs(sliderTb) do 1214 | if y == v.y then 1215 | v:click(x, y) 1216 | end 1217 | end 1218 | elseif event == "key" then 1219 | local key = eventData[2] 1220 | for k, v in pairs(fieldTb) do 1221 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1222 | v:inputKey(key) 1223 | end 1224 | end 1225 | elseif event == "char" then 1226 | local char = eventData[2] 1227 | for k, v in pairs(fieldTb) do 1228 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1229 | v:inputChar(char) 1230 | end 1231 | end 1232 | end 1233 | 1234 | -- 刷新数据到properties 1235 | system.updatePersistentData() 1236 | end 1237 | end 1238 | end 1239 | end 1240 | 1241 | local checkFire = function() 1242 | while true do 1243 | if fire and ct > 0 then 1244 | redstone.setOutput(properties.fire, controlCenter.fire) 1245 | else 1246 | redstone.setOutput(properties.fire, false) 1247 | end 1248 | sleep(0.05) 1249 | end 1250 | end 1251 | 1252 | local runCannonControl = function() 1253 | parallel.waitForAll(runCt, checkFire) 1254 | end 1255 | 1256 | local run = function() 1257 | parallel.waitForAll(runCannonControl, runTerm) 1258 | end 1259 | 1260 | parallel.waitForAll(run, runListener) 1261 | -------------------------------------------------------------------------------- /OnPhys/startup.lua: -------------------------------------------------------------------------------- 1 | peripheral.find("modem", rednet.open) 2 | 3 | local properties, system 4 | local protocol, request_protocol = "CBCNetWork", "CBCcenter" 5 | 6 | ----------------init----------------- 7 | local ANGLE_TO_SPEED = 26.6666666666666666667 8 | 9 | system = { 10 | fileName = "dat", 11 | file = nil 12 | } 13 | 14 | system.init = function() 15 | system.file = io.open(system.fileName, "r") 16 | if system.file then 17 | local tmpProp = textutils.unserialise(system.file:read("a")) 18 | properties = system.reset() 19 | for k, v in pairs(properties) do 20 | if tmpProp[k] then 21 | properties[k] = tmpProp[k] 22 | end 23 | end 24 | 25 | system.file:close() 26 | else 27 | properties = system.reset() 28 | system.updatePersistentData() 29 | end 30 | end 31 | 32 | system.reset = function() 33 | return { 34 | cannonName = "CBC", 35 | YawBearID = "-1", 36 | PitchBearID = "-1", 37 | controlCenterId = "-1", 38 | mode = "hms", 39 | power_on = "front", -- 开机信号 40 | fire = "back", -- 开火信号 41 | cannonOffset = { 42 | x = 0, 43 | y = 3, 44 | z = 0 45 | }, 46 | minPitchAngle = -90, 47 | face = "west", 48 | idleFace = "west", 49 | password = "123456", 50 | InvertYaw = false, 51 | InvertPitch = false, 52 | max_rotate_speed = 256, 53 | lock_yaw_range = "0", 54 | lock_yaw_face = "east", 55 | velocity = "160", 56 | barrelLength = "8", 57 | forecastMov = "24", 58 | forecastRot = "3.4", 59 | gravity = "0.05", 60 | drag = "0.01", 61 | P = "1", 62 | D = "0" 63 | } 64 | end 65 | 66 | system.updatePersistentData = function() 67 | system.write(system.fileName, properties) 68 | end 69 | 70 | system.write = function(file, obj) 71 | system.file = io.open(file, "w") 72 | system.file:write(textutils.serialise(obj)) 73 | system.file:close() 74 | end 75 | 76 | system.init() 77 | 78 | for i = 1, 2, 1 do 79 | redstone.setOutput(properties.power_on, false) 80 | redstone.setOutput(properties.power_on, true) 81 | end 82 | sleep(0.5) 83 | 84 | -----------function------------ 85 | local quatMultiply = function(q1, q2) 86 | local newQuat = {} 87 | newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w 88 | newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x 89 | newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y 90 | newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z 91 | return newQuat 92 | end 93 | 94 | local RotateVectorByQuat = function(quat, v) 95 | local x = quat.x * 2 96 | local y = quat.y * 2 97 | local z = quat.z * 2 98 | local xx = quat.x * x 99 | local yy = quat.y * y 100 | local zz = quat.z * z 101 | local xy = quat.x * y 102 | local xz = quat.x * z 103 | local yz = quat.y * z 104 | local wx = quat.w * x 105 | local wy = quat.w * y 106 | local wz = quat.w * z 107 | local res = {} 108 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 109 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 110 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 111 | return res 112 | end 113 | 114 | local negaQ = function(q) 115 | return { 116 | w = q.w, 117 | x = -q.x, 118 | y = -q.y, 119 | z = -q.z 120 | } 121 | end 122 | 123 | local quatList = { 124 | west = { 125 | w = -1, 126 | x = 0, 127 | y = 0, 128 | z = 0 129 | }, 130 | south = { 131 | w = -0.70710678118654752440084436210485, 132 | x = 0, 133 | y = -0.70710678118654752440084436210485, 134 | z = 0 135 | }, 136 | east = { 137 | w = 0, 138 | x = 0, 139 | y = -1, 140 | z = 0 141 | }, 142 | north = { 143 | w = -0.70710678118654752440084436210485, 144 | x = 0, 145 | y = 0.70710678118654752440084436210485, 146 | z = 0 147 | } 148 | } 149 | 150 | local copysign = function(num1, num2) 151 | num1 = math.abs(num1) 152 | num1 = num2 > 0 and num1 or -num1 153 | return num1 154 | end 155 | 156 | local genParticle = function(x, y, z) 157 | commands.execAsync(string.format("particle electric_spark %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z)) 158 | end 159 | 160 | local genStr = function(s, count) 161 | local result = "" 162 | for i = 1, count, 1 do 163 | result = result .. s 164 | end 165 | return result 166 | end 167 | 168 | local resetAngelRange = function(angle) 169 | if (math.abs(angle) > 180) then 170 | angle = math.abs(angle) >= 360 and angle % 360 or angle 171 | return -copysign(360 - math.abs(angle), angle) 172 | else 173 | return angle 174 | end 175 | end 176 | 177 | local rayCaster = { 178 | block = nil 179 | } 180 | 181 | rayCaster.run = function(start, v3Speed, range, showParticle) 182 | local vec = {} 183 | vec.x = start.x 184 | vec.y = start.y 185 | vec.z = start.z 186 | for i = 0, range, 1 do 187 | vec.x = vec.x + v3Speed.x 188 | vec.y = vec.y + v3Speed.y 189 | vec.z = vec.z + v3Speed.z 190 | 191 | rayCaster.block = coordinate.getBlock(vec.x, vec.y, vec.z - 1) 192 | if (rayCaster.block ~= "minecraft:air" or i >= range) then 193 | break 194 | end 195 | if showParticle then 196 | genParticle(vec.x, vec.y, vec.z) 197 | end 198 | end 199 | return { 200 | x = vec.x, 201 | y = vec.y, 202 | z = vec.z, 203 | name = rayCaster.block 204 | } 205 | end 206 | 207 | local getCannonPos = function() 208 | local wPos = ship.getWorldspacePosition() 209 | local yardPos = ship.getShipyardPosition() 210 | local selfPos = coordinate.getAbsoluteCoordinates() 211 | local offset = { 212 | x = yardPos.x - selfPos.x - 0.5 - properties.cannonOffset.x, 213 | y = yardPos.y - selfPos.y - 0.5 - properties.cannonOffset.y, 214 | z = yardPos.z - selfPos.z - 0.5 - properties.cannonOffset.z 215 | } 216 | offset = RotateVectorByQuat(ship.getQuaternion(), offset) 217 | return { 218 | x = wPos.x - offset.x, 219 | y = wPos.y - offset.y, 220 | z = wPos.z - offset.z 221 | } 222 | end 223 | 224 | function math.lerp(a, b, t) 225 | return a + (b - a) * t 226 | end 227 | 228 | local pdCt = function(tgYaw, omega, p, d) 229 | local result = tgYaw * p + omega * d 230 | return math.abs(result) > properties.max_rotate_speed and copysign(properties.max_rotate_speed, result) or result 231 | end 232 | 233 | local ln = function(x) 234 | return math.log(x) / math.log(math.exp(1)) 235 | end 236 | 237 | local getTime = function(dis, pitch) 238 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 239 | barrelLength = barrelLength and barrelLength or 0 240 | local cosP = math.abs(math.cos(pitch)) 241 | dis = dis - barrelLength * cosP 242 | 243 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 244 | v0 = v0 and v0 or 0 245 | 246 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 247 | drag = drag and 1 - drag or 0.99 248 | 249 | local result 250 | 251 | if drag < 0.001 or drag > 0.999 then 252 | result = dis / (cosP * v0) 253 | else 254 | result = math.abs(math.log(1 - dis / (100 * (cosP * v0))) / ln(drag)) 255 | end 256 | -- local result = math.log((dis * lnD) / (v0 * cosP) + 1, drag) 257 | 258 | return result and result or 0 259 | end 260 | 261 | local getY2 = function(t, y0, pitch) 262 | if t > 10000 then 263 | return 0 264 | end 265 | local grav = #properties.gravity == 0 and 0 or tonumber(properties.gravity) 266 | grav = grav and grav or 0.05 267 | local sinP = math.sin(pitch) 268 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 269 | barrelLength = barrelLength and barrelLength or 0 270 | y0 = barrelLength * sinP + y0 271 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 272 | v0 = v0 and v0 or 0 273 | local Vy = v0 * sinP 274 | 275 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 276 | drag = drag and 1 - drag or 0.99 277 | if drag < 0.001 then 278 | drag = 1 279 | end 280 | local index = 1 281 | local last = 0 282 | while index < t + 1 do 283 | y0 = y0 + Vy 284 | Vy = drag * Vy - grav 285 | if index == math.floor(t) then 286 | last = y0 287 | end 288 | index = index + 1 289 | end 290 | 291 | return math.lerp(last, y0, t % math.floor(t)) 292 | end 293 | 294 | local ag_binary_search = function(arr, xDis, y0, yDis) 295 | local low = 1 296 | local high = #arr 297 | local mid, time 298 | local pitch, result = 0, 0 299 | while low <= high do 300 | mid = math.floor((low + high) / 2) 301 | pitch = arr[mid] 302 | time = getTime(xDis, pitch) 303 | result = yDis - getY2(time, y0, pitch) 304 | if result >= -0.018 and result <= 0.018 then 305 | break 306 | --return mid, time 307 | elseif result > 0 then 308 | low = mid + 1 309 | else 310 | high = mid - 1 311 | end 312 | end 313 | return pitch, time 314 | end 315 | 316 | local matrixMultiplication_3d = function (m, v) 317 | return { 318 | x = m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z, 319 | y = m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z, 320 | z = m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z 321 | } 322 | end 323 | 324 | local newVec = function() 325 | return { 326 | x = 0, 327 | y = 0, 328 | z = 0 329 | } 330 | end 331 | local newQuat = function () 332 | return { 333 | w = 1, 334 | x = 0, 335 | y = 0, 336 | z = 0 337 | } 338 | end 339 | local normVector = function(v) 340 | local l = math.sqrt(v.x ^ 2 + v.y ^ 2 + v.z ^ 2) 341 | if l == 0 then 342 | return newVec() 343 | else 344 | local result = {} 345 | result.x = v.x / l 346 | result.y = v.y / l 347 | result.z = v.z / l 348 | return result 349 | end 350 | end 351 | 352 | 353 | local parent = { 354 | pos = newVec(), 355 | quat = newQuat(), 356 | omega = newVec(), 357 | velocity = newVec() 358 | } 359 | local pitchParent = { 360 | slug = "" 361 | } 362 | local controlCenter = { 363 | tgPos = newVec(), 364 | velocity = newVec(), 365 | mode = 2, 366 | fire = false 367 | } 368 | 369 | local getPD = function () 370 | local p = #properties.P == 0 and 0 or tonumber(properties.P) 371 | p = p and p or 0 372 | local d = #properties.D == 0 and 0 or tonumber(properties.D) 373 | d = d and d or 0 374 | return p, d 375 | end 376 | 377 | local getBearId = function () 378 | local YawId = #properties.YawBearID == 0 and 0 or tonumber(properties.YawBearID) 379 | local PitchId = #properties.PitchBearID == 0 and 0 or tonumber(properties.PitchBearID) 380 | YawId = YawId and YawId or 0 381 | PitchId = PitchId and PitchId or 0 382 | return YawId, PitchId 383 | end 384 | 385 | local ct = 20 386 | local listener = function() 387 | local YawId, PitchId = getBearId() 388 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 389 | controlCenterId = controlCenterId and controlCenterId or 0 390 | while true do 391 | local id, msg = rednet.receive(protocol, 2) 392 | if not id then 393 | YawId, PitchId = getBearId() 394 | elseif id == YawId then 395 | parent.quat = msg.quat 396 | parent.omega = msg.omega 397 | parent.slug = msg.slug 398 | parent.velocity = msg.velocity 399 | parent.pos = msg.pos 400 | elseif id == PitchId then 401 | pitchParent.slug = msg.slug 402 | elseif id == controlCenterId then 403 | controlCenter = msg 404 | ct = 20 405 | end 406 | end 407 | end 408 | 409 | local cross_point = newVec() 410 | local box_1 = peripheral.find("gtceu:lv_super_chest") 411 | box_1 = box_1 and box_1 or peripheral.find("gtceu:mv_super_chest") 412 | box_1 = box_1 and box_1 or peripheral.find("gtceu:hv_super_chest") 413 | box_1 = box_1 and box_1 or peripheral.find("gtceu:ev_super_chest") 414 | local box_2 = peripheral.find("create:depot") 415 | local bullets_count = 0 416 | local getBullets_count = function () 417 | while true do 418 | local box_1_count, box_2_count = 0, 0 419 | if box_2 and box_2.getItemDetail(1) then 420 | box_2_count = box_2.getItemDetail(1).count 421 | end 422 | if box_1 and box_1.getItemDetail(1) then 423 | box_1_count = box_1.getItemDetail(1).count 424 | end 425 | bullets_count = box_1_count + box_2_count 426 | if not box_1 and not box_2 then 427 | sleep(0.05) 428 | end 429 | end 430 | end 431 | 432 | local sendRequest = function() 433 | local slug = ship and ship.getName() or nil 434 | while true do 435 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 436 | controlCenterId = controlCenterId and controlCenterId or 0 437 | rednet.send(controlCenterId, { 438 | name = properties.cannonName, 439 | pw = properties.password, 440 | slug = slug, 441 | yawSlug = parent.slug, 442 | pitchSlug = pitchParent.slug, 443 | bullets_count = bullets_count, 444 | cross_point = cross_point 445 | }, request_protocol) 446 | sleep(0.05) 447 | end 448 | end 449 | 450 | local run_msgSend = function () 451 | parallel.waitForAll(sendRequest, getBullets_count) 452 | end 453 | 454 | local runListener = function() 455 | parallel.waitForAll(run_msgSend, listener) 456 | end 457 | 458 | local cannonUtil = { 459 | pos = newVec(), 460 | prePos = newVec(), 461 | velocity = newVec() 462 | } 463 | 464 | function cannonUtil:getAtt() 465 | self.pos = getCannonPos() 466 | if ship then 467 | local v = ship.getVelocity() 468 | self.velocity = { 469 | x = v.x / 20, 470 | y = v.y / 20, 471 | z = v.z / 20 472 | } 473 | else 474 | self.velocity = { 475 | x = self.pos.x - self.prePos.x, 476 | y = self.pos.y - self.prePos.y, 477 | z = self.pos.z - self.prePos.z 478 | } 479 | end 480 | 481 | self.quat = quatMultiply(quatList[properties.face], ship.getQuaternion()) 482 | end 483 | 484 | function cannonUtil:setPreAtt() 485 | self.prePos = self.pos 486 | end 487 | 488 | function cannonUtil:getNextPos(t) 489 | return { 490 | x = self.pos.x + self.velocity.x * t, 491 | y = self.pos.y + self.velocity.y * t, 492 | z = self.pos.z + self.velocity.z * t 493 | } 494 | end 495 | local vector_scale = function (v, s) 496 | return { 497 | x = v.x * s, 498 | y = v.y * s, 499 | z = v.z * s 500 | } 501 | end 502 | local getVecFromFace = function (face) 503 | if face == "west" then 504 | return {x = -1, y = 0, z = 0} 505 | elseif face == "east" then 506 | return {x = 1, y = 0, z = 0} 507 | elseif face == "north" then 508 | return {x = 0, y = 0, z = -1} 509 | elseif face == "south" then 510 | return {x = 0, y = 0, z = 1} 511 | end 512 | end 513 | 514 | local pitchList = {} 515 | for i = -90, 90, 0.0375 do 516 | table.insert(pitchList, math.rad(i)) 517 | end 518 | 519 | ------------------------------------------ 520 | 521 | local omega2Q = function (omega, tick) 522 | local omegaRot = { 523 | x = omega.x / tick, 524 | y = omega.y / tick, 525 | z = omega.z / tick 526 | } 527 | local sqrt = math.sqrt(omegaRot.x ^ 2 + omegaRot.y ^ 2 + omegaRot.z ^ 2) 528 | sqrt = math.abs(sqrt) > math.pi and copysign(math.pi, sqrt) or sqrt 529 | if sqrt ~= 0 then 530 | omegaRot.x = omegaRot.x / sqrt 531 | omegaRot.y = omegaRot.y / sqrt 532 | omegaRot.z = omegaRot.z / sqrt 533 | local halfTheta = sqrt / 2 534 | local sinHTheta = math.sin(halfTheta) 535 | return { 536 | w = math.cos(halfTheta), 537 | x = omegaRot.x * sinHTheta, 538 | y = omegaRot.y * sinHTheta, 539 | z = omegaRot.z * sinHTheta 540 | } 541 | else 542 | return nil 543 | end 544 | end 545 | 546 | local fire = false 547 | local runCt = function() 548 | while true do 549 | cannonUtil:getAtt() 550 | 551 | fire = controlCenter.fire 552 | local omega = RotateVectorByQuat(negaQ(ship.getQuaternion()), ship.getOmega()) 553 | 554 | local nextQ, pNextQ = ship.getQuaternion(), parent.quat 555 | --commands.execAsync(("say x=%0.2f, y=%0.2f, z=%0.2f"):format(pOmega.x, pOmega.y, pOmega.z)) 556 | 557 | local forecastRot = #properties.forecastRot == 0 and 0 or tonumber(properties.forecastRot) 558 | forecastRot = forecastRot and forecastRot or 16 559 | 560 | local omegaQuat = omega2Q(parent.omega, 20 / forecastRot) 561 | 562 | if omegaQuat then 563 | nextQ = quatMultiply(nextQ, omegaQuat) 564 | pNextQ = quatMultiply(pNextQ, omegaQuat) 565 | end 566 | 567 | local pErr = { 568 | x = cannonUtil.pos.x - parent.pos.x, 569 | y = cannonUtil.pos.y - parent.pos.y, 570 | z = cannonUtil.pos.z - parent.pos.z, 571 | } 572 | 573 | local pNextQ2 = parent.quat 574 | local omegaQ2 = omega2Q(parent.omega, 6 / forecastRot) 575 | if omegaQ2 then 576 | pNextQ2 = quatMultiply(pNextQ2, omegaQ2) 577 | end 578 | pErr = RotateVectorByQuat(negaQ(parent.quat), pErr) 579 | pErr = RotateVectorByQuat(pNextQ2, pErr) 580 | 581 | --commands.execAsync(("say x=%0.4f, y=%0.4f, z=%0.4f"):format(pErr.x, pErr.y, pErr.z)) 582 | 583 | local forecastMov = #properties.forecastMov == 0 and 0 or tonumber(properties.forecastMov) 584 | forecastMov = forecastMov and forecastMov or 16 585 | local cannonPos = { 586 | x = parent.pos.x + parent.velocity.x * forecastMov, 587 | y = parent.pos.y + parent.velocity.y * forecastMov, 588 | z = parent.pos.z + parent.velocity.z * forecastMov 589 | } 590 | cannonPos.x = cannonPos.x + pErr.x 591 | cannonPos.y = cannonPos.y + pErr.y 592 | cannonPos.z = cannonPos.z + pErr.z 593 | 594 | if commands then 595 | genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 596 | end 597 | 598 | if ct > 0 then 599 | ct = ct - 1 600 | local target = controlCenter.tgPos 601 | target.y = target.y + 0.5 602 | local tgVec = { 603 | x = target.x - cannonPos.x, 604 | y = target.y - cannonPos.y, 605 | z = target.z - cannonPos.z 606 | } 607 | local tmpT = (500 - math.sqrt(tgVec.x ^ 2 + tgVec.y ^ 2 + tgVec.z ^ 2)) / 500 608 | tmpT = tmpT < 0 and 0 or tmpT * 8 609 | 610 | target.x = target.x + controlCenter.velocity.x * tmpT 611 | target.y = target.y + controlCenter.velocity.y * tmpT 612 | target.z = target.z + controlCenter.velocity.z * tmpT 613 | 614 | tgVec = { 615 | x = target.x - cannonPos.x, 616 | y = target.y - cannonPos.y, 617 | z = target.z - cannonPos.z 618 | } 619 | --genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 620 | 621 | local xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 622 | local calcPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 623 | local tmpVec 624 | 625 | if cTime > 5 then 626 | if controlCenter.mode > 2 then 627 | target.x = target.x + controlCenter.velocity.x * cTime 628 | target.y = target.y + controlCenter.velocity.y * cTime 629 | target.z = target.z + controlCenter.velocity.z * cTime 630 | tgVec = { 631 | x = target.x - cannonPos.x, 632 | y = target.y - cannonPos.y, 633 | z = target.z - cannonPos.z 634 | } 635 | xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 636 | calcPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 637 | end 638 | 639 | local _c = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 640 | local allDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2 + tgVec.z ^ 2) 641 | local cosP = math.cos(calcPitch) 642 | tmpVec = { 643 | x = allDis * (tgVec.x / _c) * cosP, 644 | y = allDis * math.sin(calcPitch), 645 | z = allDis * (tgVec.z / _c) * cosP 646 | } 647 | else 648 | tmpVec = tgVec 649 | end 650 | 651 | local rot = RotateVectorByQuat(quatMultiply(quatList[properties.face], negaQ(nextQ)), tmpVec) 652 | 653 | local tmpYaw = -math.deg(math.atan2(rot.z, -rot.x)) 654 | local tmpPitch = -math.deg(math.asin(rot.y / math.sqrt(rot.x ^ 2 + rot.y ^ 2 + rot.z ^ 2))) 655 | 656 | local localVec = RotateVectorByQuat(quatMultiply(quatList[properties.lock_yaw_face], negaQ(parent.quat)), tmpVec) 657 | 658 | local yaw_range = #properties.lock_yaw_range == 0 and 0 or tonumber(properties.lock_yaw_range) 659 | yaw_range = yaw_range and yaw_range or 0 660 | local localYaw = -math.deg(math.atan2(localVec.z, -localVec.x)) 661 | local localPitch = math.deg(math.asin(localVec.y / math.sqrt(localVec.x ^ 2 + localVec.y ^ 2 + localVec.z ^ 2))) 662 | if math.abs(localYaw) < yaw_range then 663 | --tmpYaw = copysign(yaw_range, tmpYaw) 664 | fire = false 665 | tmpYaw = 0 666 | end 667 | 668 | if localPitch < properties.minPitchAngle then 669 | tmpPitch = 0 670 | fire = false 671 | end 672 | 673 | local p, d = getPD() 674 | local yawSpeed = math.floor(pdCt(tmpYaw, omega.y, p, d) + 0.5) 675 | local pitchSpeed = math.floor(pdCt(tmpPitch, -omega.z, p, d) + 0.5) 676 | 677 | if properties.InvertYaw then 678 | yawSpeed = -yawSpeed 679 | end 680 | if properties.InvertPitch then 681 | pitchSpeed = -pitchSpeed 682 | end 683 | 684 | local YawId, PitchId = getBearId() 685 | rednet.send(YawId, yawSpeed, protocol) 686 | rednet.send(PitchId, pitchSpeed, protocol) 687 | else 688 | fire = false 689 | local yPoint = RotateVectorByQuat(parent.quat,{x=0, y=1, z=0}) 690 | local point = getVecFromFace(properties.idleFace) 691 | if yPoint.y > 0 then 692 | point = getVecFromFace(properties.idleFace) 693 | else 694 | point = { x= -point.x, y=-point.y, z=-point.z} 695 | end 696 | local xP = RotateVectorByQuat(parent.quat, point) 697 | 698 | local pq = { 699 | w = cannonUtil.quat.w, 700 | x = -cannonUtil.quat.x, 701 | y = -cannonUtil.quat.y, 702 | z = -cannonUtil.quat.z 703 | } 704 | local xP2 = RotateVectorByQuat(pq, xP) 705 | local resultYaw = -math.deg(math.atan2(xP2.z, -xP2.x)) 706 | if properties.InvertYaw then 707 | resultYaw = -resultYaw 708 | end 709 | local resultPitch = -math.deg(math.asin(xP2.y / math.sqrt(xP2.x ^ 2 + xP2.y ^ 2 + xP2.z ^ 2))) 710 | 711 | if properties.InvertPitch then 712 | resultPitch = -resultPitch 713 | end 714 | 715 | local p, d = getPD() 716 | local yawSpeed = math.floor(pdCt(resultYaw, omega.y, p, d) + 0.5) 717 | local pitchSpeed = math.floor(pdCt(resultPitch, -omega.z, p, d) + 0.5) 718 | 719 | local YawId, PitchId = getBearId() 720 | rednet.send(YawId, yawSpeed, protocol) 721 | rednet.send(PitchId, pitchSpeed, protocol) 722 | end 723 | cannonUtil:setPreAtt() 724 | sleep(0.05) 725 | end 726 | end 727 | 728 | local termUtil = { 729 | cpX = 1, 730 | cpY = 1 731 | } 732 | 733 | local absTextField = { 734 | x = 1, 735 | y = 1, 736 | len = 15, 737 | text = "", 738 | textCorlor = "0", 739 | backgroundColor = "8" 740 | } 741 | 742 | function absTextField:paint() 743 | local str = "" 744 | for i = 1, self.len, 1 do 745 | local text = tostring(self.key[self.value]) 746 | local tmp = string.sub(text, i, i) 747 | if #tmp > 0 then 748 | str = str .. tmp 749 | else 750 | local tmp2 = "" 751 | for j = 0, self.len - i, 1 do 752 | tmp2 = tmp2 .. " " 753 | end 754 | str = str .. tmp2 755 | break 756 | end 757 | end 758 | 759 | term.setCursorPos(self.x, self.y) 760 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 761 | end 762 | 763 | function absTextField:inputChar(char) 764 | local xPos, yPos = term.getCursorPos() 765 | xPos = xPos + 1 - self.x 766 | local field = tostring(self.key[self.value]) 767 | if #field < self.len then 768 | if self.type == "number" then 769 | if char >= '0' and char <= '9' then 770 | if field == "0" then 771 | field = char 772 | else 773 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 774 | end 775 | 776 | self.key[self.value] = tonumber(field) 777 | termUtil.cpX = termUtil.cpX + 1 778 | end 779 | if char == '-' then 780 | self.key[self.value] = -self.key[self.value] 781 | end 782 | elseif self.type == "string" then 783 | local strEnd = string.sub(field, xPos, #field) 784 | field = string.sub(field, 1, xPos - 1) .. char .. strEnd 785 | self.key[self.value] = field 786 | termUtil.cpX = termUtil.cpX + 1 787 | end 788 | end 789 | end 790 | 791 | function absTextField:inputKey(key) 792 | local xPos, yPos = term.getCursorPos() 793 | local field = tostring(self.key[self.value]) 794 | local minXp = self.x 795 | local maxXp = minXp + #field 796 | if key == 259 or key == 261 then -- backSpace 797 | if xPos > minXp then 798 | termUtil.cpX = termUtil.cpX - 1 799 | if #field > 0 and termUtil.cpX > 1 then 800 | local index = termUtil.cpX - self.x 801 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 802 | end 803 | if self.type == "number" then 804 | local number = tonumber(field) 805 | if not number then 806 | self.key[self.value] = 0 807 | else 808 | self.key[self.value] = number 809 | end 810 | elseif self.type == "string" then 811 | self.key[self.value] = field 812 | end 813 | end 814 | elseif key == 257 or key == 335 then 815 | -- print("enter") 816 | elseif key == 262 or key == 263 then 817 | if key == 262 then 818 | termUtil.cpX = termUtil.cpX + 1 819 | elseif key == 263 then 820 | termUtil.cpX = termUtil.cpX - 1 821 | end 822 | elseif key == 264 or key == 258 then 823 | -- print("down") 824 | elseif key == 265 then 825 | -- print("up") 826 | end 827 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 828 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 829 | end 830 | 831 | function absTextField:click(x, y) 832 | local xPos = self.x 833 | if x >= xPos then 834 | if x < xPos + #tostring(self.key[self.value]) then 835 | termUtil.cpX, termUtil.cpY = x, y 836 | else 837 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 838 | end 839 | end 840 | end 841 | 842 | local newTextField = function(key, value, x, y) 843 | return setmetatable({ 844 | key = key, 845 | value = value, 846 | type = type(key[value]), 847 | x = x, 848 | y = y 849 | }, { 850 | __index = absTextField 851 | }) 852 | end 853 | 854 | local absSelectBox = { 855 | x = 1, 856 | y = 1, 857 | label = "", 858 | contents = {}, 859 | count = 0, 860 | interval = 0, 861 | fontColor = "8", 862 | backgroundColor = "f", 863 | selectColor = "e" 864 | } 865 | 866 | function absSelectBox:paint() 867 | term.setCursorPos(self.x, self.y) 868 | local select = tostring(self.key[self.value]) 869 | for i = 1, #self.contents, 1 do 870 | local str = tostring(self.contents[i]) 871 | if select == str then 872 | term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str)) 873 | else 874 | term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str)) 875 | end 876 | for j = 1, self.interval, 1 do 877 | term.write(" ") 878 | end 879 | end 880 | end 881 | 882 | function absSelectBox:click(x, y) 883 | local xPos = x + 1 - self.x 884 | local index = 0 885 | for i = 1, #self.contents, 1 do 886 | if xPos >= index and xPos <= index + #tostring(self.contents[i]) then 887 | self.key[self.value] = self.contents[i] 888 | break 889 | end 890 | index = index + #tostring(self.contents[i]) + self.interval 891 | end 892 | end 893 | 894 | local newSelectBox = function(key, value, interval, x, y, ...) 895 | return setmetatable({ 896 | key = key, 897 | value = value, 898 | interval = interval, 899 | x = x, 900 | y = y, 901 | type = type(key[value]), 902 | contents = {...} 903 | }, { 904 | __index = absSelectBox 905 | }) 906 | end 907 | 908 | local absSlider = { 909 | x = 1, 910 | y = 1, 911 | min = 0, 912 | max = 1, 913 | len = 20, 914 | fontColor = "8", 915 | backgroundColor = "f" 916 | } 917 | 918 | function absSlider:paint() 919 | local field = self.key[self.value] 920 | if field == "-" then 921 | field = 0 922 | end 923 | local maxVal = self.max - self.min 924 | local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5) 925 | xPos = xPos < 1 and 1 or xPos 926 | term.setCursorPos(self.x, self.y) 927 | for i = 1, self.len, 1 do 928 | if xPos == i then 929 | term.blit(" ", self.backgroundColor, self.fontColor) 930 | else 931 | term.blit("-", self.fontColor, self.backgroundColor) 932 | end 933 | end 934 | end 935 | 936 | function absSlider:click(x, y) 937 | local xPos = x + 1 - self.x 938 | if xPos > self.len then 939 | xPos = self.len 940 | end 941 | self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min 942 | end 943 | 944 | local newSlider = function(key, value, min, max, len, x, y) 945 | return setmetatable({ 946 | key = key, 947 | value = value, 948 | min = min, 949 | max = max, 950 | len = len, 951 | x = x, 952 | y = y 953 | }, { 954 | __index = absSlider 955 | }) 956 | end 957 | 958 | local selfId = os.getComputerID() 959 | local runTerm = function() 960 | local fieldTb = { 961 | velocity = newTextField(properties, "velocity", 11, 2), 962 | barrelLength = newTextField(properties, "barrelLength", 30, 2), 963 | YawBearID = newTextField(properties, "YawBearID", 15, 13), 964 | PitchBearID = newTextField(properties, "PitchBearID", 15, 14), 965 | minPitchAngle = newTextField(properties, "minPitchAngle", 17, 8), 966 | max_rotate_speed = newTextField(properties, "max_rotate_speed", 20, 10), 967 | lock_yaw_range = newTextField(properties, "lock_yaw_range", 20, 12), 968 | cannonOffset_x = newTextField(properties.cannonOffset, "x", 18, 7), 969 | cannonOffset_y = newTextField(properties.cannonOffset, "y", 24, 7), 970 | cannonOffset_z = newTextField(properties.cannonOffset, "z", 30, 7), 971 | P = newTextField(properties, "P", 4, 16), 972 | D = newTextField(properties, "D", 12, 16), 973 | gravity = newTextField(properties, "gravity", 45, 6), 974 | drag = newTextField(properties, "drag", 45, 7), 975 | forecastMov = newTextField(properties, "forecastMov", 46, 16), 976 | forecastRot = newTextField(properties, "forecastRot", 46, 17), 977 | cannonName = newTextField(properties, "cannonName", 14, 17), 978 | controlCenterId = newTextField(properties, "controlCenterId", 19, 18), 979 | password = newTextField(properties, "password", 37, 18) 980 | } 981 | fieldTb.velocity.len = 5 982 | fieldTb.barrelLength.len = 3 983 | fieldTb.controlCenterId.len = 5 984 | fieldTb.password.len = 14 985 | fieldTb.minPitchAngle.len = 5 986 | fieldTb.max_rotate_speed.len = 5 987 | fieldTb.lock_yaw_range.len = 5 988 | fieldTb.YawBearID.len = 5 989 | fieldTb.PitchBearID.len = 5 990 | fieldTb.cannonOffset_x.len = 3 991 | fieldTb.cannonOffset_y.len = 3 992 | fieldTb.cannonOffset_z.len = 3 993 | fieldTb.P.len = 5 994 | fieldTb.D.len = 5 995 | fieldTb.gravity.len = 6 996 | fieldTb.drag.len = 6 997 | fieldTb.forecastMov.len = 5 998 | fieldTb.forecastRot.len = 5 999 | local selectBoxTb = { 1000 | power_on = newSelectBox(properties, "power_on", 2, 12, 3, "top", "left", "right", "front", "back"), 1001 | fire = newSelectBox(properties, "fire", 2, 8, 4, "top", "left", "right", "front", "back"), 1002 | face = newSelectBox(properties, "face", 2, 8, 5, "south", "west", "north", "east"), 1003 | idleFace = newSelectBox(properties, "idleFace", 1, 8, 6, "south", "west", "north", "east"), 1004 | lock_yaw_face = newSelectBox(properties, "lock_yaw_face", 2, 27, 12, "south", "west", "north", "east"), 1005 | InvertYaw = newSelectBox(properties, "InvertYaw", 1, 41, 13, false, true), 1006 | InvertPitch = newSelectBox(properties, "InvertPitch", 1, 41, 14, false, true) 1007 | } 1008 | 1009 | local sliderTb = { 1010 | minPitchAngle = newSlider(properties, "minPitchAngle", -90, 60, 49, 2, 9), 1011 | max_rotate_speed = newSlider(properties, "max_rotate_speed", 0, 256, 49, 2, 11) 1012 | } 1013 | 1014 | local alarm_id = os.setAlarm(os.time() + 0.05) 1015 | local alarm_flag = false 1016 | term.clear() 1017 | term.setCursorPos(15, 8) 1018 | term.write("Press any key to continue") 1019 | while true do 1020 | local eventData = {os.pullEvent()} 1021 | local event = eventData[1] 1022 | if event == "mouse_up" or event == "key_up" or event == "alarm" or event == "mouse_click" or event == 1023 | "mouse_drag" or event == "key" or event == "char" then 1024 | if not alarm_flag then 1025 | alarm_flag = true 1026 | else 1027 | term.clear() 1028 | term.setCursorPos(18, 1) 1029 | printError(string.format("self id: %d", selfId)) 1030 | term.setCursorPos(2, 2) 1031 | term.write("Velocity") 1032 | term.setCursorPos(17, 2) 1033 | term.write("BarrelLength") 1034 | 1035 | term.setCursorPos(2, 3) 1036 | term.write("POWER_ON: ") 1037 | term.setCursorPos(2, 4) 1038 | term.write("FIRE: ") 1039 | 1040 | term.setCursorPos(2, 7) 1041 | term.write("CannonOffset: x= y= z=") 1042 | term.setCursorPos(36, 6) 1043 | term.write("gravity: ") 1044 | term.setCursorPos(36, 7) 1045 | term.write(" drag: ") 1046 | 1047 | term.setCursorPos(2, 8) 1048 | term.write("MinPitchAngle: ") 1049 | term.setCursorPos(2, 10) 1050 | term.write("Max_rotate_speed: ") 1051 | term.setCursorPos(2, 12) 1052 | term.write("lock_yaw_range: +-") 1053 | 1054 | term.setCursorPos(2, 5) 1055 | term.write("Face: ") 1056 | term.setCursorPos(2, 6) 1057 | term.write("IdleFace: ") 1058 | 1059 | term.setCursorPos(2, 13) 1060 | term.write("YawBearId: ") 1061 | term.setCursorPos(2, 14) 1062 | term.write("PitchBearId: ") 1063 | 1064 | term.setCursorPos(30, 13) 1065 | term.write("InvertYaw: ") 1066 | term.setCursorPos(28, 14) 1067 | term.write("InvertPitch: ") 1068 | 1069 | term.setCursorPos(2, 16) 1070 | term.write("P: ") 1071 | term.setCursorPos(10, 16) 1072 | term.write("D: ") 1073 | term.setCursorPos(34, 16) 1074 | term.write("forecastMov: ") 1075 | term.setCursorPos(34, 17) 1076 | term.write("forecastRot: ") 1077 | 1078 | term.setCursorPos(2, 17) 1079 | term.write("cannonName: ") 1080 | term.setCursorPos(2, 18) 1081 | term.write("controlCenterId: ") 1082 | term.setCursorPos(27, 18) 1083 | term.write("Password: ") 1084 | 1085 | for k, v in pairs(fieldTb) do 1086 | v:paint() 1087 | end 1088 | 1089 | for k, v in pairs(selectBoxTb) do 1090 | v:paint() 1091 | end 1092 | 1093 | for k, v in pairs(sliderTb) do 1094 | v:paint() 1095 | end 1096 | 1097 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 1098 | 1099 | if event == "mouse_click" then 1100 | term.setCursorBlink(true) 1101 | local x, y = eventData[3], eventData[4] 1102 | for k, v in pairs(fieldTb) do -- 点击了输入框 1103 | if y == v.y and x >= v.x and x <= v.x + v.len then 1104 | v:click(x, y) 1105 | end 1106 | end 1107 | for k, v in pairs(selectBoxTb) do -- 点击了选择框 1108 | if y == v.y then 1109 | v:click(x, y) 1110 | end 1111 | end 1112 | for k, v in pairs(sliderTb) do 1113 | if y == v.y then 1114 | v:click(x, y) 1115 | end 1116 | end 1117 | elseif event == "mouse_drag" then 1118 | local x, y = eventData[3], eventData[4] 1119 | for k, v in pairs(sliderTb) do 1120 | if y == v.y then 1121 | v:click(x, y) 1122 | end 1123 | end 1124 | elseif event == "key" then 1125 | local key = eventData[2] 1126 | for k, v in pairs(fieldTb) do 1127 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1128 | v:inputKey(key) 1129 | end 1130 | end 1131 | elseif event == "char" then 1132 | local char = eventData[2] 1133 | for k, v in pairs(fieldTb) do 1134 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1135 | v:inputChar(char) 1136 | end 1137 | end 1138 | end 1139 | 1140 | -- 刷新数据到properties 1141 | system.updatePersistentData() 1142 | end 1143 | end 1144 | end 1145 | end 1146 | 1147 | local checkFire = function() 1148 | while true do 1149 | if fire and ct > 0 then 1150 | redstone.setOutput(properties.fire, controlCenter.fire) 1151 | else 1152 | redstone.setOutput(properties.fire, false) 1153 | end 1154 | sleep(0.05) 1155 | end 1156 | end 1157 | 1158 | local runCannonControl = function() 1159 | parallel.waitForAll(runCt, checkFire) 1160 | end 1161 | 1162 | local run = function() 1163 | parallel.waitForAll(runCannonControl, runTerm) 1164 | end 1165 | 1166 | parallel.waitForAll(run, runListener) 1167 | -------------------------------------------------------------------------------- /OnYaw/startup.lua: -------------------------------------------------------------------------------- 1 | local gear = peripheral.find("Create_RotationSpeedController") 2 | local protocol = "CBCNetWork" 3 | peripheral.find("modem", rednet.open) 4 | 5 | local genStr = function(s, count) 6 | local result = "" 7 | for i = 1, count, 1 do 8 | result = result .. s 9 | end 10 | return result 11 | end 12 | 13 | local system, properties, parentId 14 | system = { 15 | fileName = "dat", 16 | file = nil, 17 | } 18 | 19 | system.init = function() 20 | system.file = io.open(system.fileName, "r") 21 | if system.file then 22 | local tmpProp = textutils.unserialise(system.file:read("a")) 23 | properties = system.reset() 24 | for k, v in pairs(properties) do 25 | if tmpProp[k] then 26 | properties[k] = tmpProp[k] 27 | end 28 | end 29 | 30 | system.file:close() 31 | else 32 | properties = system.reset() 33 | system.updatePersistentData() 34 | end 35 | parentId = tonumber(properties.parentId) 36 | end 37 | 38 | system.reset = function() 39 | return { 40 | parentId = "-1", 41 | } 42 | end 43 | 44 | system.updatePersistentData = function() 45 | system.write(system.fileName, properties) 46 | end 47 | 48 | system.write = function(file, obj) 49 | system.file = io.open(file, "w") 50 | system.file:write(textutils.serialise(obj)) 51 | system.file:close() 52 | end 53 | 54 | system.init() 55 | 56 | gear.setTargetSpeed(0) 57 | 58 | local termUtil = { 59 | cpX = 1, 60 | cpY = 1, 61 | } 62 | 63 | local absTextField = { 64 | x = 1, 65 | y = 1, 66 | len = 15, 67 | text = "", 68 | textCorlor = "0", 69 | backgroundColor = "8", 70 | } 71 | 72 | function absTextField:paint() 73 | local str = "" 74 | for i = 1, self.len, 1 do 75 | local text = tostring(self.key[self.value]) 76 | local tmp = string.sub(text, i, i) 77 | if #tmp > 0 then 78 | str = str .. tmp 79 | else 80 | local tmp2 = "" 81 | for j = 0, self.len - i, 1 do 82 | tmp2 = tmp2 .. " " 83 | end 84 | str = str .. tmp2 85 | break 86 | end 87 | end 88 | 89 | term.setCursorPos(self.x, self.y) 90 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 91 | end 92 | 93 | function absTextField:inputChar(char) 94 | local xPos, yPos = term.getCursorPos() 95 | xPos = xPos + 1 - self.x 96 | local field = tostring(self.key[self.value]) 97 | if #field < self.len then 98 | if self.type == "number" then 99 | if char >= '0' and char <= '9' then 100 | if field == "0" then 101 | field = char 102 | else 103 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 104 | end 105 | 106 | self.key[self.value] = tonumber(field) 107 | termUtil.cpX = termUtil.cpX + 1 108 | end 109 | if char == '-' then 110 | self.key[self.value] = -self.key[self.value] 111 | end 112 | elseif self.type == "string" then 113 | local strEnd = string.sub(field, xPos, #field) 114 | field = string.sub(field, 1, xPos) .. char .. strEnd 115 | self.key[self.value] = field 116 | termUtil.cpX = termUtil.cpX + 1 117 | end 118 | end 119 | end 120 | 121 | function absTextField:inputKey(key) 122 | local xPos, yPos = term.getCursorPos() 123 | local field = tostring(self.key[self.value]) 124 | local minXp = self.x 125 | local maxXp = minXp + #field 126 | if key == 259 or key == 261 then --backSpace 127 | if xPos > minXp then 128 | termUtil.cpX = termUtil.cpX - 1 129 | if #field > 0 and termUtil.cpX > 1 then 130 | local index = termUtil.cpX - self.x 131 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 132 | end 133 | if self.type == "number" then 134 | local number = tonumber(field) 135 | if not number then 136 | self.key[self.value] = 0 137 | else 138 | self.key[self.value] = number 139 | end 140 | elseif self.type == "string" then 141 | self.key[self.value] = field 142 | end 143 | end 144 | elseif key == 257 or key == 335 then 145 | --print("enter") 146 | elseif key == 262 or key == 263 then 147 | if key == 262 then 148 | termUtil.cpX = termUtil.cpX + 1 149 | elseif key == 263 then 150 | termUtil.cpX = termUtil.cpX - 1 151 | end 152 | elseif key == 264 or key == 258 then 153 | --print("down") 154 | elseif key == 265 then 155 | --print("up") 156 | end 157 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 158 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 159 | end 160 | 161 | function absTextField:click(x, y) 162 | local xPos = self.x 163 | if x >= xPos then 164 | if x < xPos + #tostring(self.key[self.value]) then 165 | termUtil.cpX, termUtil.cpY = x, y 166 | else 167 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 168 | end 169 | end 170 | end 171 | 172 | local newTextField = function(key, value, x, y) 173 | return setmetatable({ key = key, value = value, type = type(key[value]), x = x, y = y }, 174 | { __index = absTextField }) 175 | end 176 | 177 | function termUtil:init() 178 | self.fieldTb = { 179 | password = newTextField(properties, "parentId", 12, 3), 180 | } 181 | 182 | termUtil:refresh() 183 | end 184 | 185 | local selfId = os.getComputerID() 186 | function termUtil:refresh() 187 | term.clear() 188 | term.setCursorPos(18, 1) 189 | printError(string.format("self id: %d", selfId)) 190 | term.setCursorPos(2, 3) 191 | term.write("parentId: ") 192 | for k, v in pairs(self.fieldTb) do 193 | v:paint() 194 | end 195 | end 196 | 197 | local RotateVectorByQuat = function(quat, v) 198 | local x = quat.x * 2 199 | local y = quat.y * 2 200 | local z = quat.z * 2 201 | local xx = quat.x * x 202 | local yy = quat.y * y 203 | local zz = quat.z * z 204 | local xy = quat.x * y 205 | local xz = quat.x * z 206 | local yz = quat.y * z 207 | local wx = quat.w * x 208 | local wy = quat.w * y 209 | local wz = quat.w * z 210 | local res = {} 211 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 212 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 213 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 214 | return res 215 | end 216 | 217 | local getConjQuat = function(q) 218 | return { 219 | w = q.w, 220 | x = -q.x, 221 | y = -q.y, 222 | z = -q.z 223 | } 224 | end 225 | 226 | local id, msg 227 | local run = function () 228 | local slug = ship and ship.getName() or nil 229 | while true do 230 | repeat 231 | id, msg = rednet.receive(protocol) 232 | until id == parentId 233 | if msg ~= msg then 234 | msg = 0 235 | end 236 | gear.setTargetSpeed(msg) 237 | if parentId then 238 | local q = ship.getQuaternion() 239 | local velocity = ship.getVelocity() 240 | velocity.x = velocity.x / 20 241 | velocity.y = velocity.y / 20 242 | velocity.z = velocity.z / 20 243 | local sendMsg = { 244 | quat = q, 245 | slug = slug, 246 | omega = RotateVectorByQuat(getConjQuat(q), ship.getOmega()), 247 | velocity = velocity, 248 | pos = ship.getWorldspacePosition() 249 | } 250 | rednet.send(parentId, sendMsg, protocol) 251 | end 252 | end 253 | end 254 | 255 | local listener = function () 256 | while true do 257 | local eventData = { os.pullEvent() } 258 | local event = eventData[1] 259 | 260 | if event == "mouse_click" or event == "key" or event == "char" then 261 | if event == "mouse_click" then 262 | term.setCursorBlink(true) 263 | 264 | local x, y = eventData[3], eventData[4] 265 | for k, v in pairs(termUtil.fieldTb) do --点击了输入框 266 | if y == v.y and x >= v.x and x <= v.x + v.len then 267 | v:click(x, y) 268 | end 269 | end 270 | elseif event == "key" then 271 | local key = eventData[2] 272 | for k, v in pairs(termUtil.fieldTb) do 273 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 274 | v:inputKey(key) 275 | end 276 | end 277 | elseif event == "char" then 278 | local char = eventData[2] 279 | for k, v in pairs(termUtil.fieldTb) do 280 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 281 | v:inputChar(char) 282 | end 283 | end 284 | end 285 | 286 | --刷新数据到properties 287 | system.updatePersistentData() 288 | termUtil:refresh() 289 | parentId = tonumber(properties.parentId) 290 | 291 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 292 | end 293 | end 294 | end 295 | 296 | system.init() 297 | termUtil:init() 298 | parallel.waitForAll(run, listener) 299 | -------------------------------------------------------------------------------- /Only_CBC/startup.lua: -------------------------------------------------------------------------------- 1 | peripheral.find("modem", rednet.open) 2 | 3 | local properties, system 4 | local protocol, request_protocol = "CBCNetWork", "CBCcenter" 5 | 6 | ----------------init----------------- 7 | local ANGLE_TO_SPEED = 26.6666666666666666667 8 | 9 | system = { 10 | fileName = "dat", 11 | file = nil, 12 | } 13 | 14 | system.init = function() 15 | system.file = io.open(system.fileName, "r") 16 | if system.file then 17 | local tmpProp = textutils.unserialise(system.file:read("a")) 18 | properties = system.reset() 19 | for k, v in pairs(properties) do 20 | if tmpProp[k] then 21 | properties[k] = tmpProp[k] 22 | end 23 | end 24 | 25 | system.file:close() 26 | else 27 | properties = system.reset() 28 | system.updatePersistentData() 29 | end 30 | end 31 | 32 | system.reset = function() 33 | return { 34 | cannonName = "CBC", 35 | switchGear = false, 36 | controlCenterId = "-1", 37 | power_on = "back", --开机信号 38 | fire = "top", --开火信号 39 | cannonOffset = { x = 0, y = 3, z = 0 }, 40 | minPitchAngle = -45, 41 | face = "west", 42 | cannonFace = "west", 43 | password = "123456", 44 | InvertYaw = false, 45 | InvertPitch = false, 46 | max_rotate_speed = 256, 47 | lock_yaw_range = "0", 48 | lock_yaw_face = "east", 49 | velocity = "160", 50 | barrelLength = "8", 51 | forecast = "16", 52 | gravity = "0.05", 53 | drag = "0.01", 54 | } 55 | end 56 | 57 | system.updatePersistentData = function() 58 | system.write(system.fileName, properties) 59 | end 60 | 61 | system.write = function(file, obj) 62 | system.file = io.open(file, "w") 63 | system.file:write(textutils.serialise(obj)) 64 | system.file:close() 65 | end 66 | 67 | system.init() 68 | 69 | local gears = { peripheral.find("Create_RotationSpeedController") } 70 | local yawGear = gears[1] 71 | local pitchGear = gears[2] 72 | 73 | if not yawGear or not pitchGear then 74 | printError("Need SpeedController") 75 | else 76 | yawGear.setTargetSpeed(0) 77 | pitchGear.setTargetSpeed(0) 78 | for i = 1, 2, 1 do 79 | redstone.setOutput(properties.power_on, false) 80 | redstone.setOutput(properties.power_on, true) 81 | end 82 | sleep(0.25) 83 | end 84 | 85 | local cannon = peripheral.find("cbc_cannon_mount") 86 | if not cannon then 87 | printError("Need peripheral: cbc_cannon_mount") 88 | return 89 | end 90 | 91 | -----------function------------ 92 | local quatMultiply = function(q1, q2) 93 | local newQuat = {} 94 | newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w 95 | newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x 96 | newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y 97 | newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z 98 | return newQuat 99 | end 100 | 101 | local RotateVectorByQuat = function(quat, v) 102 | local x = quat.x * 2 103 | local y = quat.y * 2 104 | local z = quat.z * 2 105 | local xx = quat.x * x 106 | local yy = quat.y * y 107 | local zz = quat.z * z 108 | local xy = quat.x * y 109 | local xz = quat.x * z 110 | local yz = quat.y * z 111 | local wx = quat.w * x 112 | local wy = quat.w * y 113 | local wz = quat.w * z 114 | local res = {} 115 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 116 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 117 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 118 | return res 119 | end 120 | local negaQ = function(q) 121 | return { 122 | w = q.w, 123 | x = -q.x, 124 | y = -q.y, 125 | z = -q.z 126 | } 127 | end 128 | 129 | local matrixMultiplication_3d = function (m, v) 130 | return { 131 | x = m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z, 132 | y = m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z, 133 | z = m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z 134 | } 135 | end 136 | 137 | local newVec = function() 138 | return { 139 | x = 0, 140 | y = 0, 141 | z = 0 142 | } 143 | end 144 | local newQuat = function () 145 | return { 146 | w = 1, 147 | x = 0, 148 | y = 0, 149 | z = 0 150 | } 151 | end 152 | local normVector = function(v) 153 | local l = math.sqrt(v.x ^ 2 + v.y ^ 2 + v.z ^ 2) 154 | if l == 0 then 155 | return newVec() 156 | else 157 | local result = {} 158 | result.x = v.x / l 159 | result.y = v.y / l 160 | result.z = v.z / l 161 | return result 162 | end 163 | end 164 | function vector.scale(v, s) 165 | return { 166 | x = v.x * s, 167 | y = v.y * s, 168 | z = v.z * s 169 | } 170 | end 171 | local getVecFromFace = function (face) 172 | if face == "west" then 173 | return {x = -1, y = 0, z = 0} 174 | elseif face == "east" then 175 | return {x = 1, y = 0, z = 0} 176 | elseif face == "north" then 177 | return {x = 0, y = 0, z = -1} 178 | elseif face == "south" then 179 | return {x = 0, y = 0, z = 1} 180 | end 181 | end 182 | 183 | local copysign = function(num1, num2) 184 | num1 = math.abs(num1) 185 | num1 = num2 > 0 and num1 or -num1 186 | return num1 187 | end 188 | 189 | local genParticle = function(x, y, z) 190 | commands.execAsync(string.format("particle electric_spark %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z)) 191 | end 192 | 193 | local genStr = function(s, count) 194 | local result = "" 195 | for i = 1, count, 1 do 196 | result = result .. s 197 | end 198 | return result 199 | end 200 | 201 | local resetAngelRange = function(angle) 202 | if (math.abs(angle) > 180) then 203 | angle = math.abs(angle) >= 360 and angle % 360 or angle 204 | return -copysign(360 - math.abs(angle), angle) 205 | else 206 | return angle 207 | end 208 | end 209 | 210 | local rayCaster = { 211 | block = nil 212 | } 213 | 214 | rayCaster.run = function(start, v3Speed, range, showParticle) 215 | local vec = {} 216 | vec.x = start.x 217 | vec.y = start.y 218 | vec.z = start.z 219 | for i = 0, range, 1 do 220 | vec.x = vec.x + v3Speed.x 221 | vec.y = vec.y + v3Speed.y 222 | vec.z = vec.z + v3Speed.z 223 | 224 | rayCaster.block = coordinate.getBlock(vec.x, vec.y, vec.z - 1) 225 | if (rayCaster.block ~= "minecraft:air" or i >= range) then 226 | break 227 | end 228 | if showParticle then 229 | genParticle(vec.x, vec.y, vec.z) 230 | end 231 | end 232 | return { x = vec.x, y = vec.y, z = vec.z, name = rayCaster.block } 233 | end 234 | 235 | local getCannonPos = function() 236 | local wPos = ship.getWorldspacePosition() 237 | local yardPos = ship.getShipyardPosition() 238 | local selfPos = coordinate.getAbsoluteCoordinates() 239 | local offset = { 240 | x = yardPos.x - selfPos.x - 0.5 - properties.cannonOffset.x, 241 | y = yardPos.y - selfPos.y - 0.5 - properties.cannonOffset.y, 242 | z = yardPos.z - selfPos.z - 0.5 - properties.cannonOffset.z 243 | } 244 | offset = RotateVectorByQuat(ship.getQuaternion(), offset) 245 | return { 246 | x = wPos.x - offset.x, 247 | y = wPos.y - offset.y, 248 | z = wPos.z - offset.z 249 | } 250 | end 251 | 252 | function math.lerp(a, b, t) 253 | return a + (b - a) * t 254 | end 255 | 256 | local pdCt = function(tgYaw, omega, p, d) 257 | local result = tgYaw * p + omega * d 258 | return math.abs(result) > properties.max_rotate_speed and copysign(properties.max_rotate_speed, result) or result 259 | end 260 | 261 | local ln = function(x) 262 | return math.log(x) / math.log(math.exp(1)) 263 | end 264 | 265 | local getTime = function(dis, pitch) 266 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 267 | barrelLength = barrelLength and barrelLength or 0 268 | local cosP = math.abs(math.cos(pitch)) 269 | dis = dis - barrelLength * cosP 270 | 271 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 272 | v0 = v0 and v0 or 0 273 | 274 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 275 | drag = drag and 1 - drag or 0.99 276 | 277 | local result 278 | 279 | if drag < 0.001 or drag > 0.999 then 280 | result = dis / (cosP * v0) 281 | else 282 | result = math.abs(math.log(1 - dis / (100 * (cosP * v0))) / ln(drag)) 283 | end 284 | -- local result = math.log((dis * lnD) / (v0 * cosP) + 1, drag) 285 | 286 | return result and result or 0 287 | end 288 | 289 | local getY2 = function(t, y0, pitch) 290 | if t > 10000 then 291 | return 0 292 | end 293 | local grav = #properties.gravity == 0 and 0 or tonumber(properties.gravity) 294 | grav = grav and grav or 0.05 295 | local sinP = math.sin(pitch) 296 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 297 | barrelLength = barrelLength and barrelLength or 0 298 | y0 = barrelLength * sinP + y0 299 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 300 | v0 = v0 and v0 or 0 301 | local Vy = v0 * sinP 302 | 303 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 304 | drag = drag and 1 - drag or 0.99 305 | if drag < 0.001 then 306 | drag = 1 307 | end 308 | local index = 1 309 | local last = 0 310 | while index < t + 1 do 311 | y0 = y0 + Vy 312 | Vy = drag * Vy - grav 313 | if index == math.floor(t) then 314 | last = y0 315 | end 316 | index = index + 1 317 | end 318 | 319 | return math.lerp(last, y0, t % math.floor(t)) 320 | end 321 | 322 | local ag_binary_search = function(arr, xDis, y0, yDis) 323 | local low = 1 324 | local high = #arr 325 | local mid, time 326 | local pitch, result = 0, 0 327 | while low <= high do 328 | mid = math.floor((low + high) / 2) 329 | pitch = arr[mid] 330 | time = getTime(xDis, pitch) 331 | result = yDis - getY2(time, y0, pitch) 332 | if result >= -0.018 and result <= 0.018 then 333 | break 334 | --return mid, time 335 | elseif result > 0 then 336 | low = mid + 1 337 | else 338 | high = mid - 1 339 | end 340 | end 341 | return pitch, time 342 | end 343 | 344 | local controlCenter = { tgPos = { x = 0, y = 0, z = 0 }, velocity = { x = 0, y = 0, z = 0 }, mode = 2, fire = false } 345 | local ct = 20 346 | local listener = function() 347 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 348 | controlCenterId = controlCenterId and controlCenterId or 0 349 | while true do 350 | local id, msg = rednet.receive(protocol, 2) 351 | if id == controlCenterId then 352 | controlCenter = msg 353 | ct = 20 354 | end 355 | end 356 | end 357 | 358 | local cross_point = newVec() 359 | local box_1 = peripheral.find("gtceu:lv_super_chest") 360 | box_1 = box_1 and box_1 or peripheral.find("gtceu:mv_super_chest") 361 | box_1 = box_1 and box_1 or peripheral.find("gtceu:hv_super_chest") 362 | box_1 = box_1 and box_1 or peripheral.find("gtceu:ev_super_chest") 363 | local box_2 = peripheral.find("create:depot") 364 | local bullets_count = 0 365 | local getBullets_count = function () 366 | while true do 367 | local box_1_count, box_2_count = 0, 0 368 | if box_2 and box_2.getItemDetail(1) then 369 | box_2_count = box_2.getItemDetail(1).count 370 | end 371 | if box_1 and box_1.getItemDetail(1) then 372 | box_1_count = box_1.getItemDetail(1).count 373 | end 374 | bullets_count = box_1_count + box_2_count 375 | if not box_1 and not box_2 then 376 | sleep(0.05) 377 | end 378 | end 379 | end 380 | 381 | local sendRequest = function() 382 | local slug = ship and ship.getName() or nil 383 | while true do 384 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 385 | controlCenterId = controlCenterId and controlCenterId or 0 386 | rednet.send(controlCenterId, { 387 | name = properties.cannonName, 388 | pw = properties.password, 389 | slug = slug, 390 | bullets_count = bullets_count, 391 | cross_point = cross_point 392 | }, request_protocol) 393 | sleep(0.05) 394 | end 395 | end 396 | 397 | local run_msgSend = function () 398 | parallel.waitForAll(sendRequest, getBullets_count) 399 | end 400 | 401 | local runListener = function() 402 | parallel.waitForAll(run_msgSend, listener) 403 | end 404 | 405 | 406 | local quatList = { 407 | west = { w = -1, x = 0, y = 0, z = 0 }, 408 | south = { w = -0.70710678118654752440084436210485, x = 0, y = -0.70710678118654752440084436210485, z = 0 }, 409 | east = { w = 0, x = 0, y = -1, z = 0 }, 410 | north = { w = -0.70710678118654752440084436210485, x = 0, y = 0.70710678118654752440084436210485, z = 0 }, 411 | } 412 | 413 | local cannonUtil = { 414 | pos = { x = 0, y = 0, z = 0 }, 415 | prePos = { x = 0, y = 0, z = 0 }, 416 | velocity = { x = 0, y = 0, z = 0 }, 417 | preVel = { x = 0, y = 0, z = 0 } 418 | } 419 | 420 | function cannonUtil:getAtt() 421 | self.pos = getCannonPos() 422 | local v = ship.getVelocity() 423 | self.velocity = { 424 | x = v.x / 20, 425 | y = v.y / 20, 426 | z = v.z / 20, 427 | } 428 | 429 | self.quat = quatMultiply(quatList[properties.face], ship.getQuaternion()) 430 | end 431 | 432 | function cannonUtil:setPreAtt() 433 | self.prePos = self.pos 434 | self.preVel = self.velocity 435 | end 436 | 437 | function cannonUtil:getNextPos(t) 438 | return { 439 | x = self.pos.x + self.velocity.x * t, 440 | y = self.pos.y + self.velocity.y * t, 441 | z = self.pos.z + self.velocity.z * t, 442 | } 443 | end 444 | 445 | local ysp, psp 446 | local send2Yaw = function() 447 | local n = properties.switchGear and 1 or 2 448 | gears[n].setTargetSpeed(ysp) 449 | end 450 | 451 | local send2Pitch = function() 452 | local n = properties.switchGear and 2 or 1 453 | gears[n].setTargetSpeed(psp) 454 | end 455 | 456 | local sendToGear = function(y, p) 457 | ysp, psp = y, p 458 | parallel.waitForAll(send2Yaw, send2Pitch) 459 | end 460 | 461 | local pitchList = {} 462 | for i = -90, 90, 0.0375 do 463 | table.insert(pitchList, math.rad(i)) 464 | end 465 | 466 | 467 | ------------------------------------------ 468 | 469 | local finalYaw, finalPit = 0, 0 470 | cannon.yaw = 0 471 | cannon.pitch = 0 472 | local fire = false 473 | local runCt = function() 474 | while true do 475 | cannonUtil:getAtt() 476 | local omega = RotateVectorByQuat(negaQ(ship.getQuaternion()), ship.getOmega()) 477 | local yawSpeed, pitchSpeed 478 | local tgPitch = 0 479 | local MAX_ROTATE_SPEED = properties.max_rotate_speed 480 | 481 | if ct > 0 then 482 | ct = ct - 1 483 | local forecast = #properties.forecast == 0 and 0 or tonumber(properties.forecast) 484 | forecast = forecast and forecast or 16 485 | local cannonPos = cannonUtil:getNextPos(forecast) 486 | if commands then 487 | genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 488 | end 489 | local target = controlCenter.tgPos 490 | target.y = target.y + 0.5 491 | local tgVec = { 492 | x = target.x - cannonPos.x, 493 | y = target.y - cannonPos.y, 494 | z = target.z - cannonPos.z 495 | } 496 | local tmpT = (500 - math.sqrt(tgVec.x ^ 2 + tgVec.y ^ 2 + tgVec.z ^ 2)) / 500 497 | tmpT = tmpT < 0 and 0 or tmpT * 8 498 | 499 | target.x = target.x + controlCenter.velocity.x * tmpT 500 | target.y = target.y + controlCenter.velocity.y * tmpT 501 | target.z = target.z + controlCenter.velocity.z * tmpT 502 | 503 | tgVec = { 504 | x = target.x - cannonPos.x, 505 | y = target.y - cannonPos.y, 506 | z = target.z - cannonPos.z 507 | } 508 | --genParticle(cannonPos.x, cannonPos.y, cannonPos.z) 509 | 510 | local xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 511 | local tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 512 | local tmpVec 513 | 514 | if cTime > 5 then 515 | if controlCenter.mode > 2 then 516 | target.x = target.x + controlCenter.velocity.x * cTime 517 | target.y = target.y + controlCenter.velocity.y * cTime 518 | target.z = target.z + controlCenter.velocity.z * cTime 519 | tgVec = { 520 | x = target.x - cannonPos.x, 521 | y = target.y - cannonPos.y, 522 | z = target.z - cannonPos.z 523 | } 524 | xDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 525 | tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, tgVec.y) 526 | end 527 | 528 | local _c = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2) 529 | local allDis = math.sqrt(tgVec.x ^ 2 + tgVec.z ^ 2 + tgVec.z ^ 2) 530 | local cosP = math.cos(tmpPitch) 531 | tmpVec = { 532 | x = allDis * (tgVec.x / _c) * cosP, 533 | y = allDis * math.sin(tmpPitch), 534 | z = allDis * (tgVec.z / _c) * cosP 535 | } 536 | else 537 | tmpVec = tgVec 538 | end 539 | 540 | local rot 541 | 542 | local omegaRot = { 543 | x = omega.x / 20, 544 | y = omega.y / 20, 545 | z = omega.z / 20, 546 | } 547 | local sqrt = math.sqrt(omegaRot.x ^ 2 + omegaRot.y ^ 2 + omegaRot.z ^ 2) 548 | sqrt = math.abs(sqrt) > math.pi and copysign(math.pi, sqrt) or sqrt 549 | 550 | if sqrt ~= 0 then 551 | omegaRot.x = omegaRot.x / sqrt 552 | omegaRot.y = omegaRot.y / sqrt 553 | omegaRot.z = omegaRot.z / sqrt 554 | local halfTheta = sqrt / 2 555 | local sinHTheta = math.sin(halfTheta) 556 | local omegaQuat = { 557 | w = math.cos(halfTheta), 558 | x = omegaRot.x * sinHTheta, 559 | y = omegaRot.y * sinHTheta, 560 | z = omegaRot.z * sinHTheta 561 | } 562 | 563 | local count = math.floor(forecast / 4) 564 | local nextQ = ship.getQuaternion() 565 | for i = 1, count, 1 do 566 | nextQ = quatMultiply(nextQ, omegaQuat) 567 | end 568 | rot = RotateVectorByQuat(quatMultiply(quatList[properties.face], negaQ(nextQ)), tmpVec) 569 | else 570 | rot = RotateVectorByQuat(quatMultiply(quatList[properties.face], negaQ(ship.getQuaternion())), tmpVec) 571 | end 572 | 573 | local tmpYaw = math.deg(math.atan2(rot.z, -rot.x)) 574 | local localVec = RotateVectorByQuat(quatMultiply(quatList[properties.lock_yaw_face], negaQ(ship.getQuaternion())), tmpVec) 575 | 576 | local yaw_range = #properties.lock_yaw_range == 0 and 0 or tonumber(properties.lock_yaw_range) 577 | yaw_range = yaw_range and yaw_range or 0 578 | local localYaw = -math.deg(math.atan2(localVec.z, -localVec.x)) 579 | 580 | if math.abs(localYaw) < yaw_range then 581 | tmpYaw = 0 582 | end 583 | 584 | if properties.InvertYaw then 585 | tmpYaw = resetAngelRange(cannon.yaw - tmpYaw) 586 | else 587 | tmpYaw = resetAngelRange(tmpYaw - cannon.yaw) 588 | end 589 | 590 | if math.abs(tmpYaw) > 10 then 591 | fire = false 592 | end 593 | 594 | tmpYaw = math.abs(tmpYaw) > 0.01875 and tmpYaw or 0 595 | yawSpeed = math.floor(tmpYaw * ANGLE_TO_SPEED / 2 + 0.5) 596 | yawSpeed = math.abs(yawSpeed) < MAX_ROTATE_SPEED and yawSpeed or copysign(MAX_ROTATE_SPEED, yawSpeed) 597 | 598 | ------self(pitch)------- 599 | tgPitch = math.deg(math.asin(rot.y / math.sqrt(rot.x ^ 2 + rot.y ^ 2 + rot.z ^ 2))) 600 | local tgDis = math.sqrt(tmpVec.x ^ 2 + tmpVec.y ^ 2 + tmpVec.z ^ 2) 601 | local tgPoint = vector.scale(getVecFromFace(properties.face), -tgDis) 602 | 603 | local p_angle = math.rad(cannon.pitch) / 2 604 | local pitchQuat = { 605 | w = math.cos(p_angle), 606 | x = 0, 607 | y = 0, 608 | z = math.sin(p_angle) 609 | } 610 | 611 | local y_angle = -math.rad(cannon.yaw) / 2 612 | local yawQuat = { 613 | w = math.cos(y_angle), 614 | x = 0, 615 | y = math.sin(y_angle), 616 | z = 0 617 | } 618 | cross_point = RotateVectorByQuat(quatMultiply(pitchQuat, yawQuat), tgPoint) 619 | cross_point = RotateVectorByQuat(ship.getQuaternion(), cross_point) 620 | local selfpos = cannonUtil:getNextPos(1) 621 | cross_point.x = cross_point.x + selfpos.x 622 | cross_point.y = cross_point.y + selfpos.y 623 | cross_point.z = cross_point.z + selfpos.z 624 | else 625 | local tmpYaw = properties.InvertYaw and cannon.yaw or -cannon.yaw 626 | tmpYaw = math.abs(tmpYaw) > 0.01875 and tmpYaw or 0 627 | yawSpeed = math.floor(tmpYaw * ANGLE_TO_SPEED / 2 + 0.5) 628 | yawSpeed = math.abs(yawSpeed) < MAX_ROTATE_SPEED and yawSpeed or copysign(MAX_ROTATE_SPEED, yawSpeed) 629 | cross_point = nil 630 | end 631 | 632 | if tgPitch < properties.minPitchAngle then 633 | tgPitch = properties.minPitchAngle 634 | fire = false 635 | else 636 | fire = controlCenter.fire 637 | end 638 | 639 | if properties.InvertPitch then 640 | tgPitch = resetAngelRange(tgPitch - cannon.pitch) 641 | else 642 | tgPitch = resetAngelRange(cannon.pitch - tgPitch) 643 | end 644 | 645 | if math.abs(tgPitch) > 5 then 646 | fire = false 647 | end 648 | 649 | tgPitch = math.abs(tgPitch) > 0.01875 and tgPitch or 0 650 | pitchSpeed = math.floor(tgPitch * ANGLE_TO_SPEED / 2 + 0.5) 651 | pitchSpeed = math.abs(pitchSpeed) < properties.max_rotate_speed and pitchSpeed or 652 | copysign(properties.max_rotate_speed, pitchSpeed) 653 | 654 | sendToGear(yawSpeed, pitchSpeed) 655 | 656 | local cannonPitch, cannonYaw = cannon.getPitch(), cannon.getYaw() 657 | cannonUtil:setPreAtt() 658 | if yawSpeed == 0 and pitchSpeed == 0 then 659 | local cosP = math.cos(math.rad(cannonPitch)) 660 | local xp = math.sin(math.rad(cannonYaw)) * cosP 661 | local zp = math.cos(math.rad(cannonYaw)) * cosP 662 | local yp = math.sin(math.rad(cannonPitch)) 663 | local newP = RotateVectorByQuat(quatList[properties.cannonFace], { x = xp, y = yp, z = zp }) 664 | cannon.yaw = math.deg(math.atan2(newP.z, newP.x)) 665 | cannon.pitch = math.deg(math.asin(yp)) 666 | else 667 | finalYaw = math.floor((yawSpeed / ANGLE_TO_SPEED) * 1000000 + 0.5) / 1000000 668 | if properties.InvertYaw then 669 | finalYaw = -finalYaw 670 | end 671 | cannon.yaw = resetAngelRange(cannon.yaw + finalYaw) 672 | finalPit = -math.floor((pitchSpeed / ANGLE_TO_SPEED) * 1000000 + 0.5) / 1000000 673 | if properties.InvertPitch then 674 | finalPit = -finalPit 675 | end 676 | cannon.pitch = resetAngelRange(cannon.pitch + finalPit) 677 | end 678 | end 679 | end 680 | 681 | local termUtil = { 682 | cpX = 1, 683 | cpY = 1, 684 | } 685 | 686 | local absTextField = { 687 | x = 1, 688 | y = 1, 689 | len = 15, 690 | text = "", 691 | textCorlor = "0", 692 | backgroundColor = "8", 693 | } 694 | 695 | function absTextField:paint() 696 | local str = "" 697 | for i = 1, self.len, 1 do 698 | local text = tostring(self.key[self.value]) 699 | local tmp = string.sub(text, i, i) 700 | if #tmp > 0 then 701 | str = str .. tmp 702 | else 703 | local tmp2 = "" 704 | for j = 0, self.len - i, 1 do 705 | tmp2 = tmp2 .. " " 706 | end 707 | str = str .. tmp2 708 | break 709 | end 710 | end 711 | 712 | term.setCursorPos(self.x, self.y) 713 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 714 | end 715 | 716 | function absTextField:inputChar(char) 717 | local xPos, yPos = term.getCursorPos() 718 | xPos = xPos + 1 - self.x 719 | local field = tostring(self.key[self.value]) 720 | if #field < self.len then 721 | if self.type == "number" then 722 | if char >= '0' and char <= '9' then 723 | if field == "0" then 724 | field = char 725 | else 726 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 727 | end 728 | 729 | self.key[self.value] = tonumber(field) 730 | termUtil.cpX = termUtil.cpX + 1 731 | end 732 | if char == '-' then 733 | self.key[self.value] = -self.key[self.value] 734 | end 735 | elseif self.type == "string" then 736 | local strEnd = string.sub(field, xPos, #field) 737 | field = string.sub(field, 1, xPos - 1) .. char .. strEnd 738 | self.key[self.value] = field 739 | termUtil.cpX = termUtil.cpX + 1 740 | end 741 | end 742 | end 743 | 744 | function absTextField:inputKey(key) 745 | local xPos, yPos = term.getCursorPos() 746 | local field = tostring(self.key[self.value]) 747 | local minXp = self.x 748 | local maxXp = minXp + #field 749 | if key == 259 or key == 261 then --backSpace 750 | if xPos > minXp then 751 | termUtil.cpX = termUtil.cpX - 1 752 | if #field > 0 and termUtil.cpX > 1 then 753 | local index = termUtil.cpX - self.x 754 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 755 | end 756 | if self.type == "number" then 757 | local number = tonumber(field) 758 | if not number then 759 | self.key[self.value] = 0 760 | else 761 | self.key[self.value] = number 762 | end 763 | elseif self.type == "string" then 764 | self.key[self.value] = field 765 | end 766 | end 767 | elseif key == 257 or key == 335 then 768 | --print("enter") 769 | elseif key == 262 or key == 263 then 770 | if key == 262 then 771 | termUtil.cpX = termUtil.cpX + 1 772 | elseif key == 263 then 773 | termUtil.cpX = termUtil.cpX - 1 774 | end 775 | elseif key == 264 or key == 258 then 776 | --print("down") 777 | elseif key == 265 then 778 | --print("up") 779 | end 780 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 781 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 782 | end 783 | 784 | function absTextField:click(x, y) 785 | local xPos = self.x 786 | if x >= xPos then 787 | if x < xPos + #tostring(self.key[self.value]) then 788 | termUtil.cpX, termUtil.cpY = x, y 789 | else 790 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 791 | end 792 | end 793 | end 794 | 795 | local newTextField = function(key, value, x, y) 796 | return setmetatable({ key = key, value = value, type = type(key[value]), x = x, y = y }, 797 | { __index = absTextField }) 798 | end 799 | 800 | local absSelectBox = { 801 | x = 1, 802 | y = 1, 803 | label = "", 804 | contents = {}, 805 | count = 0, 806 | interval = 0, 807 | fontColor = "8", 808 | backgroundColor = "f", 809 | selectColor = "e" 810 | } 811 | 812 | function absSelectBox:paint() 813 | term.setCursorPos(self.x, self.y) 814 | local select = tostring(self.key[self.value]) 815 | for i = 1, #self.contents, 1 do 816 | local str = tostring(self.contents[i]) 817 | if select == str then 818 | term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str)) 819 | else 820 | term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str)) 821 | end 822 | for j = 1, self.interval, 1 do 823 | term.write(" ") 824 | end 825 | end 826 | end 827 | 828 | function absSelectBox:click(x, y) 829 | local xPos = x + 1 - self.x 830 | local index = 0 831 | for i = 1, #self.contents, 1 do 832 | if xPos >= index and xPos <= index + #tostring(self.contents[i]) then 833 | self.key[self.value] = self.contents[i] 834 | break 835 | end 836 | index = index + #tostring(self.contents[i]) + self.interval 837 | end 838 | end 839 | 840 | local newSelectBox = function(key, value, interval, x, y, ...) 841 | return setmetatable( 842 | { key = key, value = value, interval = interval, x = x, y = y, type = type(key[value]), contents = { ... } }, 843 | { __index = absSelectBox }) 844 | end 845 | 846 | local absSlider = { 847 | x = 1, 848 | y = 1, 849 | min = 0, 850 | max = 1, 851 | len = 20, 852 | fontColor = "8", 853 | backgroundColor = "f" 854 | } 855 | 856 | function absSlider:paint() 857 | local field = self.key[self.value] 858 | if field == "-" then 859 | field = 0 860 | end 861 | local maxVal = self.max - self.min 862 | local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5) 863 | xPos = xPos < 1 and 1 or xPos 864 | term.setCursorPos(self.x, self.y) 865 | for i = 1, self.len, 1 do 866 | if xPos == i then 867 | term.blit(" ", self.backgroundColor, self.fontColor) 868 | else 869 | term.blit("-", self.fontColor, self.backgroundColor) 870 | end 871 | end 872 | end 873 | 874 | function absSlider:click(x, y) 875 | local xPos = x + 1 - self.x 876 | if xPos > self.len then 877 | xPos = self.len 878 | end 879 | self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min 880 | end 881 | 882 | local newSlider = function(key, value, min, max, len, x, y) 883 | return setmetatable({ key = key, value = value, min = min, max = max, len = len, x = x, y = y }, 884 | { __index = absSlider }) 885 | end 886 | 887 | local selfId = os.getComputerID() 888 | local runTerm = function() 889 | local fieldTb = { 890 | velocity = newTextField(properties, "velocity", 11, 2), 891 | barrelLength = newTextField(properties, "barrelLength", 30, 2), 892 | minPitchAngle = newTextField(properties, "minPitchAngle", 17, 8), 893 | max_rotate_speed = newTextField(properties, "max_rotate_speed", 20, 10), 894 | lock_yaw_range = newTextField(properties, "lock_yaw_range", 20, 12), 895 | cannonOffset_x = newTextField(properties.cannonOffset, "x", 18, 6), 896 | cannonOffset_y = newTextField(properties.cannonOffset, "y", 24, 6), 897 | cannonOffset_z = newTextField(properties.cannonOffset, "z", 30, 6), 898 | gravity = newTextField(properties, "gravity", 45, 6), 899 | drag = newTextField(properties, "drag", 45, 7), 900 | forecast = newTextField(properties, "forecast", 46, 2), 901 | cannonName = newTextField(properties, "cannonName", 14, 17), 902 | controlCenterId = newTextField(properties, "controlCenterId", 19, 18), 903 | password = newTextField(properties, "password", 37, 18), 904 | } 905 | fieldTb.velocity.len = 5 906 | fieldTb.barrelLength.len = 3 907 | fieldTb.controlCenterId.len = 5 908 | fieldTb.password.len = 14 909 | fieldTb.minPitchAngle.len = 5 910 | fieldTb.max_rotate_speed.len = 5 911 | fieldTb.lock_yaw_range.len = 5 912 | fieldTb.cannonOffset_x.len = 3 913 | fieldTb.cannonOffset_y.len = 3 914 | fieldTb.cannonOffset_z.len = 3 915 | fieldTb.gravity.len = 6 916 | fieldTb.drag.len = 6 917 | fieldTb.forecast.len = 5 918 | local selectBoxTb = { 919 | power_on = newSelectBox(properties, "power_on", 2, 12, 3, "top", "left", "right", "front", "back"), 920 | fire = newSelectBox(properties, "fire", 2, 8, 4, "top", "left", "right", "front", "back"), 921 | face = newSelectBox(properties, "face", 2, 8, 5, "south", "west", "north", "east"), 922 | lock_yaw_face = newSelectBox(properties, "lock_yaw_face", 2, 27, 12, "south", "west", "north", "east"), 923 | InvertYaw = newSelectBox(properties, "InvertYaw", 1, 41, 13, false, true), 924 | InvertPitch = newSelectBox(properties, "InvertPitch", 1, 15, 13, false, true), 925 | switchGear = newSelectBox(properties, "switchGear", 2, 14, 14, false, true), 926 | cannonFace = newSelectBox(properties, "cannonFace", 2, 14, 15, "south", "west", "north", "east"), 927 | } 928 | 929 | local sliderTb = { 930 | minPitchAngle = newSlider(properties, "minPitchAngle", -45, 60, 49, 2, 9), 931 | max_rotate_speed = newSlider(properties, "max_rotate_speed", 0, 256, 49, 2, 11), 932 | } 933 | 934 | local alarm_id = os.setAlarm(os.time() + 0.05) 935 | local alarm_flag = false 936 | term.clear() 937 | term.setCursorPos(15, 8) 938 | term.write("Press any key to continue") 939 | while true do 940 | local eventData = { os.pullEvent() } 941 | local event = eventData[1] 942 | if event == "mouse_up" or event == "key_up" or event == "alarm" 943 | or event == "mouse_click" or event == "mouse_drag" or event == "key" or event == "char" then 944 | if not alarm_flag then 945 | alarm_flag = true 946 | else 947 | term.clear() 948 | term.setCursorPos(18, 1) 949 | printError(string.format("self id: %d", selfId)) 950 | term.setCursorPos(2, 2) 951 | term.write("Velocity") 952 | term.setCursorPos(17, 2) 953 | term.write("BarrelLength") 954 | term.setCursorPos(36, 2) 955 | term.write("forecast:") 956 | 957 | term.setCursorPos(2, 3) 958 | term.write("POWER_ON: ") 959 | term.setCursorPos(2, 4) 960 | term.write("FIRE: ") 961 | 962 | term.setCursorPos(2, 6) 963 | term.write("CannonOffset: x= y= z=") 964 | term.setCursorPos(36, 6) 965 | term.write("gravity: ") 966 | term.setCursorPos(36, 7) 967 | term.write(" drag: ") 968 | 969 | term.setCursorPos(2, 8) 970 | term.write("MinPitchAngle: ") 971 | term.setCursorPos(2, 10) 972 | term.write("Max_rotate_speed: ") 973 | term.setCursorPos(2, 12) 974 | term.write("lock_yaw_range: +-") 975 | 976 | term.setCursorPos(2, 5) 977 | term.write("Face: ") 978 | 979 | term.setCursorPos(2, 13) 980 | term.write("InvertPitch: ") 981 | term.setCursorPos(30, 13) 982 | term.write("InvertYaw: ") 983 | 984 | term.setCursorPos(2, 14) 985 | term.write("switchGear:") 986 | term.setCursorPos(2, 15) 987 | term.write("CannonFace:") 988 | 989 | term.setCursorPos(2, 17) 990 | term.write("cannonName: ") 991 | term.setCursorPos(2, 18) 992 | term.write("controlCenterId: ") 993 | term.setCursorPos(27, 18) 994 | term.write("Password: ") 995 | 996 | for k, v in pairs(fieldTb) do 997 | v:paint() 998 | end 999 | 1000 | for k, v in pairs(selectBoxTb) do 1001 | v:paint() 1002 | end 1003 | 1004 | for k, v in pairs(sliderTb) do 1005 | v:paint() 1006 | end 1007 | 1008 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 1009 | 1010 | if event == "mouse_click" then 1011 | term.setCursorBlink(true) 1012 | local x, y = eventData[3], eventData[4] 1013 | for k, v in pairs(fieldTb) do --点击了输入框 1014 | if y == v.y and x >= v.x and x <= v.x + v.len then 1015 | v:click(x, y) 1016 | end 1017 | end 1018 | for k, v in pairs(selectBoxTb) do --点击了选择框 1019 | if y == v.y then 1020 | v:click(x, y) 1021 | end 1022 | end 1023 | for k, v in pairs(sliderTb) do 1024 | if y == v.y then 1025 | v:click(x, y) 1026 | end 1027 | end 1028 | elseif event == "mouse_drag" then 1029 | local x, y = eventData[3], eventData[4] 1030 | for k, v in pairs(sliderTb) do 1031 | if y == v.y then 1032 | v:click(x, y) 1033 | end 1034 | end 1035 | elseif event == "key" then 1036 | local key = eventData[2] 1037 | for k, v in pairs(fieldTb) do 1038 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1039 | v:inputKey(key) 1040 | end 1041 | end 1042 | elseif event == "char" then 1043 | local char = eventData[2] 1044 | for k, v in pairs(fieldTb) do 1045 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1046 | v:inputChar(char) 1047 | end 1048 | end 1049 | end 1050 | 1051 | --刷新数据到properties 1052 | system.updatePersistentData() 1053 | end 1054 | end 1055 | end 1056 | end 1057 | 1058 | local checkFire = function() 1059 | while true do 1060 | if fire and ct > 0 then 1061 | if not redstone.getOutput(properties.fire) then 1062 | redstone.setOutput(properties.fire, true) 1063 | end 1064 | else 1065 | if redstone.getOutput(properties.fire) then 1066 | redstone.setOutput(properties.fire, false) 1067 | end 1068 | end 1069 | sleep(0.05) 1070 | end 1071 | end 1072 | 1073 | local runCannonControl = function() 1074 | parallel.waitForAll(runCt, checkFire) 1075 | end 1076 | 1077 | local run = function() 1078 | parallel.waitForAll(runCannonControl, runTerm) 1079 | end 1080 | 1081 | parallel.waitForAll(run, runListener) 1082 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 这是一款基于Valkyrie skies 2、CBC、CC:Tweaked、CCVS的火控计算机 2 | ### This is a fire control computer based on Valkyrie skies 2, CBC, CC:Tweaked, CCVS 3 | 4 | 火控需要玄学Mod作为API (0.0.4+): 5 | Fire control requires Metaphysics Mod(0.0.4+) as API: 6 | > @kallen https://github.com/KallenKas024/Metaphysics/tree/main 7 | 8 | 该火控需要依赖以下mod: 9 | This fire control also requires the following mods: 10 | 11 | * Valkyrien Skies 2 12 | * Create 13 | * Create Big Cannons 14 | * CC:Tweaked 15 | * CC:VS 16 | * Tom's Peripherals 17 | * Metaphysics 18 | * VS Addition 19 | 20 | 以下mod非强制,但推荐安装 21 | The following mods are not mandatory, but recommended to install 22 | * clockwork 23 | * vMod 24 | * Some Peripherals 25 | * Create: Interactive 26 | * UnlimitedPeripheralWorks(推荐,提供外设代理-无线外设) (reason: peripheral proxy) 27 | * Some Peripherals (火控的头瞄模式2支持Raycast眼镜) (Fire control head aiming mode 2 supports Raycast goggles) 28 | -------------------------------------------------------------------------------- /command_computer_only/startup.lua: -------------------------------------------------------------------------------- 1 | if not ship then 2 | printError("Need CC:VS!") 3 | return 4 | end 5 | peripheral.find("modem", rednet.open) 6 | 7 | local properties, system 8 | local protocol, request_protocol = "CBCNetWork", "CBCcenter" 9 | 10 | ----------------init----------------- 11 | local ANGLE_TO_SPEED = 26.6666666666666666667 12 | 13 | system = { 14 | fileName = "dat", 15 | file = nil 16 | } 17 | 18 | system.init = function() 19 | system.file = io.open(system.fileName, "r") 20 | if system.file then 21 | local tmpProp = textutils.unserialise(system.file:read("a")) 22 | properties = system.reset() 23 | for k, v in pairs(properties) do 24 | if tmpProp[k] then 25 | properties[k] = tmpProp[k] 26 | end 27 | end 28 | 29 | system.file:close() 30 | else 31 | properties = system.reset() 32 | system.updatePersistentData() 33 | end 34 | end 35 | 36 | system.reset = function() 37 | return { 38 | cannonName = "CBC", 39 | controlCenterId = "-1", 40 | power_on = "front", -- 开机信号 41 | fire = "back", -- 开火信号 42 | inversion = false, 43 | basic_v = true, 44 | minPitchAngle = -45, 45 | idleFace = "west", 46 | password = "123456", 47 | min_pitch = "-45", 48 | max_pitch = "90", 49 | min_yaw = "-180", 50 | max_yaw = "180", 51 | velocity = "160", 52 | barrelLength = "8", 53 | forecastMov = "1.5", 54 | forecastRot = "3", 55 | gravity = "0.05", 56 | drag = "0.01", 57 | } 58 | end 59 | 60 | system.updatePersistentData = function() 61 | system.write(system.fileName, properties) 62 | end 63 | 64 | system.write = function(file, obj) 65 | system.file = io.open(file, "w") 66 | system.file:write(textutils.serialise(obj)) 67 | system.file:close() 68 | end 69 | 70 | system.init() 71 | 72 | ------------------------ 73 | 74 | local genStr = function(s, count) 75 | local result = "" 76 | for i = 1, count, 1 do 77 | result = result .. s 78 | end 79 | return result 80 | end 81 | 82 | local square = function (num) 83 | return num * num 84 | end 85 | 86 | local vector = {} 87 | local newVec = function (x, y, z) 88 | if type(x) == "table" then 89 | return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector }) 90 | elseif x and y and z then 91 | return setmetatable({ x = x, y = y, z = z}, { __index = vector}) 92 | else 93 | return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector}) 94 | end 95 | end 96 | 97 | function vector:zero() 98 | self.x = 0 99 | self.y = 0 100 | self.z = 0 101 | return self 102 | end 103 | 104 | function vector:copy() 105 | return newVec(self.x, self.y, self.z) 106 | end 107 | 108 | function vector:len() 109 | return math.sqrt(square(self.x) + square(self.y) + square(self.z) ) 110 | end 111 | 112 | function vector:norm() 113 | local l = self:len() 114 | if l == 0 then 115 | self:zero() 116 | else 117 | self.x = self.x / l 118 | self.y = self.y / l 119 | self.z = self.z / l 120 | end 121 | return self 122 | end 123 | 124 | function vector:nega() 125 | self.x = -self.x 126 | self.y = -self.y 127 | self.z = -self.z 128 | return self 129 | end 130 | 131 | function vector:add(v) 132 | self.x = self.x + v.x 133 | self.y = self.y + v.y 134 | self.z = self.z + v.z 135 | return self 136 | end 137 | 138 | function vector:sub(v) 139 | self.x = self.x - v.x 140 | self.y = self.y - v.y 141 | self.z = self.z - v.z 142 | return self 143 | end 144 | 145 | function vector:scale(num) 146 | self.x = self.x * num 147 | self.y = self.y * num 148 | self.z = self.z * num 149 | return self 150 | end 151 | 152 | function vector:unpack() 153 | return self.x, self.y, self.z 154 | end 155 | 156 | local vector_scale = function(v, s) 157 | return { 158 | x = v.x * s, 159 | y = v.y * s, 160 | z = v.z * s 161 | } 162 | end 163 | local unpackVec = function(v) 164 | return v.x, v.y, v.z 165 | end 166 | local quat = {} 167 | function quat.new() 168 | return { w = 1, x = 0, y = 0, z = 0} 169 | end 170 | 171 | function quat.vecRot(q, v) 172 | local x = q.x * 2 173 | local y = q.y * 2 174 | local z = q.z * 2 175 | local xx = q.x * x 176 | local yy = q.y * y 177 | local zz = q.z * z 178 | local xy = q.x * y 179 | local xz = q.x * z 180 | local yz = q.y * z 181 | local wx = q.w * x 182 | local wy = q.w * y 183 | local wz = q.w * z 184 | local res = {} 185 | res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z 186 | res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z 187 | res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z 188 | return newVec(res.x, res.y, res.z) 189 | end 190 | 191 | function quat.multiply(q1, q2) 192 | local newQuat = {} 193 | newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w 194 | newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x 195 | newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y 196 | newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z 197 | return newQuat 198 | end 199 | 200 | function quat.nega(q) 201 | return { 202 | w = q.w, 203 | x = -q.x, 204 | y = -q.y, 205 | z = -q.z, 206 | } 207 | end 208 | 209 | local newQuat = function(w, x, y, z) 210 | return setmetatable({ w = w, x = x, y = y, z = z }, { __index = quat }) 211 | end 212 | 213 | local sin2cos = function(s) 214 | return math.sqrt( 1 - square(s)) 215 | end 216 | 217 | local copysign = function(num1, num2) 218 | num1 = math.abs(num1) 219 | num1 = num2 > 0 and num1 or -num1 220 | return num1 221 | end 222 | 223 | local omega2Q = function (omega, tick) 224 | local omegaRot = { 225 | x = omega.x * tick, 226 | y = omega.y * tick * 2, 227 | z = omega.z * tick 228 | } 229 | local sqrt = math.sqrt(omegaRot.x ^ 2 + omegaRot.y ^ 2 + omegaRot.z ^ 2) 230 | sqrt = math.abs(sqrt) > math.pi and copysign(math.pi, sqrt) or sqrt 231 | if sqrt ~= 0 then 232 | omegaRot.x = omegaRot.x / sqrt 233 | omegaRot.y = omegaRot.y / sqrt 234 | omegaRot.z = omegaRot.z / sqrt 235 | local halfTheta = sqrt / 2 236 | local sinHTheta = math.sin(halfTheta) 237 | return { 238 | w = math.cos(halfTheta), 239 | x = omegaRot.x * sinHTheta, 240 | y = omegaRot.y * sinHTheta, 241 | z = omegaRot.z * sinHTheta 242 | } 243 | else 244 | return nil 245 | end 246 | end 247 | 248 | local matrixMultiplication_3d = function (m, v) 249 | return newVec( 250 | m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z, 251 | m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z, 252 | m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z 253 | ) 254 | end 255 | 256 | local resetAngelRange = function(angle) 257 | if (math.abs(angle) > 180) then 258 | angle = math.abs(angle) >= 360 and angle % 360 or angle 259 | return -copysign(360 - math.abs(angle), angle) 260 | else 261 | return angle 262 | end 263 | end 264 | 265 | local cannon, fire 266 | local targetAngle = { pitch = 0, yaw = 0 } 267 | local block_offset = newVec(0.5, 0.5, 0.5) 268 | 269 | local faces = { 270 | up = newVec(0, 1, 0), 271 | down = newVec(0, -1, 0), 272 | north = newVec(0, 0, -1), 273 | south = newVec(0, 0, 1), 274 | west = newVec(-1, 0, 0), 275 | east = newVec(1, 0, 0) 276 | } 277 | local selfPos = newVec(coordinate.getAbsoluteCoordinates()) 278 | --commands.execAsync(("say %.2f %.2f %.2f"):format(selfPos.x, selfPos.y, selfPos.z)) 279 | 280 | local getCannon = function (pos) 281 | local _, str = commands.exec(("data get block %d %d %d"):format(pos.x, pos.y, pos.z)) 282 | str = str[1] 283 | local yaw = str:match("CannonYaw: [-]?(%d+%.?%d*)f") 284 | local result = nil 285 | if yaw then 286 | result = {} 287 | result.yaw = resetAngelRange(tonumber(yaw)) 288 | result.initYaw = math.floor(result.yaw + 0.5) 289 | result.pitch = tonumber(str:match("CannonPitch: [-]?(%d+%.?%d*)f")) 290 | result.initPitch = result.pitch 291 | result.pos = pos 292 | end 293 | return result, str 294 | end 295 | 296 | local initIdleAg = function () 297 | if properties.idleFace == "west" then 298 | cannon.idle_yaw = 90 299 | elseif properties.idleFace == "east" then 300 | cannon.idle_yaw = -90 301 | elseif properties.idleFace == "north" then 302 | cannon.idle_yaw = 180 303 | else 304 | cannon.idle_yaw = 0 305 | end 306 | end 307 | 308 | local initCannon = function () 309 | for k, v in pairs(faces) do 310 | local tPos = newVec(selfPos):add(v) 311 | cannon, str = getCannon(tPos) 312 | if cannon then 313 | redstone.setOutput(properties.power_on, false) 314 | redstone.setOutput(properties.power_on, true) 315 | selfPos = tPos 316 | cannon.type = str:match('id:%s*"([^"]*)"') 317 | if cannon.type == "createbigcannons:cannon_mount" then 318 | --commands.execAsync("say here") 319 | cannon.cross_offset = newVec(0, 2, 0) 320 | if properties.inversion == true then 321 | cannon.cross_offset.y = -2 322 | end 323 | else 324 | if cannon.initYaw == 0 then 325 | cannon.cross_offset = newVec(-1, 0, 0) 326 | elseif cannon.initYaw == 180 then 327 | cannon.cross_offset = newVec(1, 0, 0) 328 | elseif cannon.initYaw == 90 then 329 | cannon.cross_offset = newVec(0, 0, -1) 330 | else 331 | cannon.cross_offset = newVec(0, 0, 1) 332 | end 333 | end 334 | initIdleAg() 335 | cannon.face = k 336 | cannon.faceVec = { 337 | x = -math.floor(math.sin(math.rad(cannon.yaw)) + 0.5), 338 | y = 0, 339 | z = math.floor(math.cos(math.rad(cannon.yaw)) + 0.5) 340 | } 341 | cannon.faceMatrix = { 342 | {cannon.faceVec.x, 0, cannon.faceVec.z}, 343 | {0, 1, 0}, 344 | {-cannon.faceVec.z, 0, cannon.faceVec.x}, 345 | } 346 | break 347 | end 348 | end 349 | end 350 | initCannon() 351 | 352 | local setYawAndPitch = function (yaw, pitch) 353 | commands.execAsync(("data modify block %d %d %d CannonYaw set value %.4f"):format(selfPos.x, selfPos.y, selfPos.z, yaw)) 354 | commands.execAsync(("data modify block %d %d %d CannonPitch set value %.4f"):format(selfPos.x, selfPos.y, selfPos.z, pitch)) 355 | end 356 | 357 | local getCannonPos = function() 358 | local wPos = newVec(ship.getWorldspacePosition()) 359 | local yardPos = newVec(ship.getShipyardPosition()) 360 | local offset = yardPos:sub(selfPos):sub(block_offset):sub(cannon.cross_offset) 361 | offset = quat.vecRot(ship.getQuaternion(), offset) 362 | return wPos:sub(offset) 363 | end 364 | 365 | local controlCenter = { 366 | tgPos = newVec(), 367 | velocity = newVec(), 368 | mode = 2, 369 | fire = false 370 | } 371 | 372 | local ct = 20 373 | local listener = function() 374 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 375 | controlCenterId = controlCenterId and controlCenterId or 0 376 | while true do 377 | local id, msg = rednet.receive(protocol, 2) 378 | if id == controlCenterId then 379 | controlCenter = msg 380 | ct = 20 381 | end 382 | end 383 | end 384 | 385 | local box_1 = peripheral.find("gtceu:lv_super_chest") 386 | box_1 = box_1 and box_1 or peripheral.find("gtceu:mv_super_chest") 387 | box_1 = box_1 and box_1 or peripheral.find("gtceu:hv_super_chest") 388 | box_1 = box_1 and box_1 or peripheral.find("gtceu:ev_super_chest") 389 | local bullets_count = 0 390 | local getBullets_count = function () 391 | while true do 392 | if box_1 and box_1.getItemDetail(1) then 393 | bullets_count = box_1.getItemDetail(1).count 394 | else 395 | bullets_count = 0 396 | end 397 | if not box_1 then 398 | sleep(0.05) 399 | end 400 | end 401 | end 402 | 403 | local cross_point = newVec() 404 | local sendRequest = function() 405 | local slug = ship and ship.getName() or nil 406 | while true do 407 | local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId) 408 | controlCenterId = controlCenterId and controlCenterId or 0 409 | rednet.send(controlCenterId, { 410 | name = properties.cannonName, 411 | pw = properties.password, 412 | bullets_count = bullets_count, 413 | cross_point = cross_point 414 | }, request_protocol) 415 | sleep(0.05) 416 | end 417 | end 418 | 419 | local getFashaodesu = function() 420 | local players = coordinate.getPlayers() 421 | for k, v in pairs(players) do 422 | if v.name == "fashaodesu" then 423 | return v 424 | end 425 | end 426 | end 427 | 428 | function math.lerp(a, b, t) 429 | return a + (b - a) * t 430 | end 431 | 432 | local pdCt = function(tgYaw, omega, p, d) 433 | local result = tgYaw * p + omega * d 434 | return math.abs(result) > properties.max_rotate_speed and copysign(properties.max_rotate_speed, result) or result 435 | end 436 | 437 | local ln = function(x) 438 | return math.log(x) / math.log(math.exp(1)) 439 | end 440 | 441 | local getTime = function(dis, pitch) 442 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 443 | barrelLength = barrelLength and barrelLength or 0 444 | local cosP = math.abs(math.cos(pitch)) 445 | dis = dis - barrelLength * cosP 446 | 447 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 448 | v0 = v0 and v0 or 0 449 | 450 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 451 | drag = drag and 1 - drag or 0.99 452 | 453 | local result 454 | 455 | if drag < 0.001 or drag > 0.999 then 456 | result = dis / (cosP * v0) 457 | else 458 | result = math.abs(math.log(1 - dis / (100 * (cosP * v0))) / ln(drag)) 459 | end 460 | -- local result = math.log((dis * lnD) / (v0 * cosP) + 1, drag) 461 | 462 | return result and result or 0 463 | end 464 | 465 | local getY2 = function(t, y0, pitch) 466 | if t > 10000 then 467 | return 0 468 | end 469 | local grav = #properties.gravity == 0 and 0 or tonumber(properties.gravity) 470 | grav = grav and grav or 0.05 471 | local sinP = math.sin(pitch) 472 | local barrelLength = #properties.barrelLength == 0 and 0 or tonumber(properties.barrelLength) 473 | barrelLength = barrelLength and barrelLength or 0 474 | y0 = barrelLength * sinP + y0 475 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 20 476 | v0 = v0 and v0 or 0 477 | local Vy = v0 * sinP 478 | 479 | local drag = #properties.drag == 0 and 0 or tonumber(properties.drag) 480 | drag = drag and 1 - drag or 0.99 481 | if drag < 0.001 then 482 | drag = 1 483 | end 484 | local index = 1 485 | local last = 0 486 | while index < t + 1 do 487 | y0 = y0 + Vy 488 | Vy = drag * Vy - grav 489 | if index == math.floor(t) then 490 | last = y0 491 | end 492 | index = index + 1 493 | end 494 | 495 | return math.lerp(last, y0, t % math.floor(t)) 496 | end 497 | 498 | local ag_binary_search = function(arr, xDis, y0, yDis) 499 | local low = 1 500 | local high = #arr 501 | local mid, time 502 | local pitch, result = 0, 0 503 | while low <= high do 504 | mid = math.floor((low + high) / 2) 505 | pitch = arr[mid] 506 | time = getTime(xDis, pitch) 507 | result = yDis - getY2(time, y0, pitch) 508 | if result >= -0.018 and result <= 0.018 then 509 | break 510 | --return mid, time 511 | elseif result > 0 then 512 | low = mid + 1 513 | else 514 | high = mid - 1 515 | end 516 | end 517 | return pitch, time 518 | end 519 | 520 | local pitchList = {} 521 | for i = -90, 90, 0.0375 do 522 | table.insert(pitchList, math.rad(i)) 523 | end 524 | local genParticle = function(x, y, z) 525 | commands.execAsync(string.format("particle electric_spark %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z)) 526 | end 527 | 528 | local run_cannon = function() 529 | sleep(0.1) 530 | while true do 531 | local cannon_pos = getCannonPos() 532 | 533 | local final_point 534 | local target_point = newVec() 535 | if ct > 0 and controlCenter.omega then 536 | ct = ct - 1 537 | fire = controlCenter.fire 538 | local forecast = #properties.forecastMov == 0 and 1 or tonumber(properties.forecastMov) 539 | forecast = forecast and forecast or 1 540 | local forecastRot = #properties.forecastRot == 0 and 1 or tonumber(properties.forecastRot) 541 | forecastRot = forecastRot and forecastRot or 1 542 | 543 | local parentOmega = quat.vecRot(quat.nega(ship.getQuaternion()), controlCenter.omega):scale(0.01) 544 | 545 | local nextQ = ship.getQuaternion() 546 | local omegaQuat = omega2Q(parentOmega, forecastRot) 547 | if omegaQuat then 548 | nextQ = quat.multiply(nextQ, omegaQuat) 549 | end 550 | 551 | local pErr = cannon_pos:copy():sub(controlCenter.pos) 552 | 553 | local pNextQ2 = controlCenter.rot 554 | local omegaQ2 = omega2Q(parentOmega, forecastRot * 2) 555 | if omegaQ2 then 556 | pNextQ2 = quat.multiply(pNextQ2, omegaQ2) 557 | end 558 | pErr = quat.vecRot(quat.nega(controlCenter.rot), pErr) 559 | pErr = quat.vecRot(pNextQ2, pErr) 560 | 561 | local parent_velocity = newVec(controlCenter.center_velocity):scale(0.05) 562 | local self_velocity = newVec(ship.getVelocity()):scale(0.05) 563 | 564 | local new_pos = newVec(controlCenter.pos):add(parent_velocity:scale(forecast)) 565 | new_pos:add(pErr) 566 | 567 | genParticle(new_pos.x, new_pos.y, new_pos.z) 568 | 569 | local target = newVec(controlCenter.tgPos) 570 | target.y = target.y + 0.5 571 | local target_velocity = newVec(controlCenter.velocity) 572 | --local target = newVec(getFashaodesu()) 573 | 574 | target_point = target:copy():sub(new_pos) 575 | 576 | if properties.basic_v == true then 577 | local tmpT = (500 - math.sqrt(square(target_point.x) + square(target_point.y) + square(target_point.z))) / 500 578 | tmpT = tmpT < 0 and 0 or tmpT * 8 579 | target:add(target_velocity:copy():scale(tmpT)) 580 | end 581 | local v0 = #properties.velocity == 0 and 0 or tonumber(properties.velocity) / 8 582 | v0 = v0 and v0 or 0 583 | local tmpT2 = math.sqrt(square(target_point.x) + square(target_point.y) + square(target_point.z)) / v0 584 | tmpT2 = tmpT2 < 0 and 0 or tmpT2 585 | new_pos:add(parent_velocity:scale(tmpT2)) 586 | target_point = target:copy():sub(new_pos) 587 | 588 | local xDis = math.sqrt(square(target_point.x) + square(target_point.z)) 589 | local tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, target_point.y) 590 | local tmpVec 591 | 592 | if cTime > 10 then 593 | local _c = math.sqrt(square(target_point.x) + square(target_point.z)) 594 | if controlCenter.mode > 2 then 595 | target:add(target_velocity:copy():scale(cTime)) 596 | target_point = target:copy():sub(new_pos) 597 | 598 | xDis = math.sqrt(square(target_point.x) + square(target_point.z)) 599 | _c = xDis 600 | tmpPitch, cTime = ag_binary_search(pitchList, xDis, 0, target_point.y) 601 | end 602 | 603 | local allDis = target_point:len() 604 | local cosP = math.cos(tmpPitch) 605 | tmpVec = newVec( 606 | allDis * (target_point.x / _c) * cosP, 607 | allDis * math.sin(tmpPitch), 608 | allDis * (target_point.z / _c) * cosP 609 | ) 610 | else 611 | tmpVec = target_point 612 | end 613 | 614 | final_point = quat.vecRot(quat.nega(nextQ), tmpVec:copy():add(cannon_pos):sub(new_pos)) 615 | 616 | local target_vector = final_point:norm() 617 | targetAngle.pitch = math.deg(math.asin(target_vector.y)) 618 | targetAngle.yaw = -math.deg(math.atan2(target_vector.x, target_vector.z)) 619 | 620 | local min_pitch, max_pitch = tonumber(properties.min_pitch), tonumber(properties.max_pitch) 621 | min_pitch = min_pitch and min_pitch or 0 622 | max_pitch = max_pitch and max_pitch or 0 623 | local min_yaw, max_yaw = tonumber(properties.min_yaw), tonumber(properties.max_yaw) 624 | min_yaw = min_yaw and min_yaw or 0 625 | max_yaw = max_yaw and max_yaw or 0 626 | targetAngle.pitch = targetAngle.pitch < min_pitch and min_pitch or targetAngle.pitch > max_pitch and max_pitch or targetAngle.pitch 627 | targetAngle.yaw = targetAngle.yaw < min_yaw and min_yaw or targetAngle.yaw > max_yaw and max_yaw or targetAngle.yaw 628 | 629 | local rot_point = matrixMultiplication_3d(cannon.faceMatrix, target_vector) 630 | local yaw_with_rot = math.deg(math.atan2(rot_point.z, rot_point.x)) 631 | local pitch_with_rot = math.deg(math.asin(rot_point.y)) 632 | local tgPoint = vector_scale(cannon.faceVec, tmpVec:len()) 633 | 634 | local p_angle = -math.rad(pitch_with_rot) / 2 635 | local pitchQuat = { 636 | w = math.cos(p_angle), 637 | x = 0, 638 | y = 0, 639 | z = math.sin(p_angle) 640 | } 641 | 642 | local y_angle = -math.rad(yaw_with_rot) / 2 643 | local yawQuat = { 644 | w = math.cos(y_angle), 645 | x = 0, 646 | y = math.sin(y_angle), 647 | z = 0 648 | } 649 | cross_point = quat.vecRot(quat.multiply(pitchQuat, yawQuat), tgPoint) 650 | cross_point = quat.vecRot(ship.getQuaternion(), cross_point) 651 | cross_point:add(cannon_pos:add(self_velocity)) 652 | --local target_point = quat.vecRot(quat.nega(ship_rot), target:copy():sub(new_pos)) 653 | else 654 | targetAngle.pitch = 0 655 | targetAngle.yaw = cannon.idle_yaw 656 | cross_point = nil 657 | end 658 | 659 | setYawAndPitch(targetAngle.yaw, targetAngle.pitch) 660 | sleep(0.05) 661 | end 662 | end 663 | 664 | local termUtil = { 665 | cpX = 1, 666 | cpY = 1 667 | } 668 | 669 | local absTextField = { 670 | x = 1, 671 | y = 1, 672 | len = 15, 673 | text = "", 674 | textCorlor = "0", 675 | backgroundColor = "8" 676 | } 677 | 678 | function absTextField:paint() 679 | local str = "" 680 | for i = 1, self.len, 1 do 681 | local text = tostring(self.key[self.value]) 682 | local tmp = string.sub(text, i, i) 683 | if #tmp > 0 then 684 | str = str .. tmp 685 | else 686 | local tmp2 = "" 687 | for j = 0, self.len - i, 1 do 688 | tmp2 = tmp2 .. " " 689 | end 690 | str = str .. tmp2 691 | break 692 | end 693 | end 694 | 695 | term.setCursorPos(self.x, self.y) 696 | term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str)) 697 | end 698 | 699 | function absTextField:inputChar(char) 700 | local xPos, yPos = term.getCursorPos() 701 | xPos = xPos + 1 - self.x 702 | local field = tostring(self.key[self.value]) 703 | if #field < self.len then 704 | if self.type == "number" then 705 | if char >= '0' and char <= '9' then 706 | if field == "0" then 707 | field = char 708 | else 709 | field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field) 710 | end 711 | 712 | self.key[self.value] = tonumber(field) 713 | termUtil.cpX = termUtil.cpX + 1 714 | end 715 | if char == '-' then 716 | self.key[self.value] = -self.key[self.value] 717 | end 718 | elseif self.type == "string" then 719 | local strEnd = string.sub(field, xPos, #field) 720 | field = string.sub(field, 1, xPos - 1) .. char .. strEnd 721 | self.key[self.value] = field 722 | termUtil.cpX = termUtil.cpX + 1 723 | end 724 | end 725 | end 726 | 727 | function absTextField:inputKey(key) 728 | local xPos, yPos = term.getCursorPos() 729 | local field = tostring(self.key[self.value]) 730 | local minXp = self.x 731 | local maxXp = minXp + #field 732 | if key == 259 or key == 261 then -- backSpace 733 | if xPos > minXp then 734 | termUtil.cpX = termUtil.cpX - 1 735 | if #field > 0 and termUtil.cpX > 1 then 736 | local index = termUtil.cpX - self.x 737 | field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field) 738 | end 739 | if self.type == "number" then 740 | local number = tonumber(field) 741 | if not number then 742 | self.key[self.value] = 0 743 | else 744 | self.key[self.value] = number 745 | end 746 | elseif self.type == "string" then 747 | self.key[self.value] = field 748 | end 749 | end 750 | elseif key == 257 or key == 335 then 751 | -- print("enter") 752 | elseif key == 262 or key == 263 then 753 | if key == 262 then 754 | termUtil.cpX = termUtil.cpX + 1 755 | elseif key == 263 then 756 | termUtil.cpX = termUtil.cpX - 1 757 | end 758 | elseif key == 264 or key == 258 then 759 | -- print("down") 760 | elseif key == 265 then 761 | -- print("up") 762 | end 763 | termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX 764 | termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX 765 | end 766 | 767 | function absTextField:click(x, y) 768 | local xPos = self.x 769 | if x >= xPos then 770 | if x < xPos + #tostring(self.key[self.value]) then 771 | termUtil.cpX, termUtil.cpY = x, y 772 | else 773 | termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y 774 | end 775 | end 776 | end 777 | 778 | local newTextField = function(key, value, x, y) 779 | return setmetatable({ 780 | key = key, 781 | value = value, 782 | type = type(key[value]), 783 | x = x, 784 | y = y 785 | }, { 786 | __index = absTextField 787 | }) 788 | end 789 | 790 | local absSelectBox = { 791 | x = 1, 792 | y = 1, 793 | label = "", 794 | contents = {}, 795 | count = 0, 796 | interval = 0, 797 | fontColor = "8", 798 | backgroundColor = "f", 799 | selectColor = "e" 800 | } 801 | 802 | function absSelectBox:paint() 803 | term.setCursorPos(self.x, self.y) 804 | local select = tostring(self.key[self.value]) 805 | for i = 1, #self.contents, 1 do 806 | local str = tostring(self.contents[i]) 807 | if select == str then 808 | term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str)) 809 | else 810 | term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str)) 811 | end 812 | for j = 1, self.interval, 1 do 813 | term.write(" ") 814 | end 815 | end 816 | end 817 | 818 | function absSelectBox:click(x, y) 819 | local xPos = x + 1 - self.x 820 | local index = 0 821 | for i = 1, #self.contents, 1 do 822 | if xPos >= index and xPos <= index + #tostring(self.contents[i]) then 823 | self.key[self.value] = self.contents[i] 824 | break 825 | end 826 | index = index + #tostring(self.contents[i]) + self.interval 827 | end 828 | end 829 | 830 | local newSelectBox = function(key, value, interval, x, y, ...) 831 | return setmetatable({ 832 | key = key, 833 | value = value, 834 | interval = interval, 835 | x = x, 836 | y = y, 837 | type = type(key[value]), 838 | contents = {...} 839 | }, { 840 | __index = absSelectBox 841 | }) 842 | end 843 | 844 | local absSlider = { 845 | x = 1, 846 | y = 1, 847 | min = 0, 848 | max = 1, 849 | len = 20, 850 | fontColor = "8", 851 | backgroundColor = "f" 852 | } 853 | 854 | function absSlider:paint() 855 | local field = self.key[self.value] 856 | if field == "-" then 857 | field = 0 858 | end 859 | local maxVal = self.max - self.min 860 | local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5) 861 | xPos = xPos < 1 and 1 or xPos 862 | term.setCursorPos(self.x, self.y) 863 | for i = 1, self.len, 1 do 864 | if xPos == i then 865 | term.blit(" ", self.backgroundColor, self.fontColor) 866 | else 867 | term.blit("-", self.fontColor, self.backgroundColor) 868 | end 869 | end 870 | end 871 | 872 | function absSlider:click(x, y) 873 | local xPos = x + 1 - self.x 874 | if xPos > self.len then 875 | xPos = self.len 876 | end 877 | self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min 878 | end 879 | 880 | local newSlider = function(key, value, min, max, len, x, y) 881 | return setmetatable({ 882 | key = key, 883 | value = value, 884 | min = min, 885 | max = max, 886 | len = len, 887 | x = x, 888 | y = y 889 | }, { 890 | __index = absSlider 891 | }) 892 | end 893 | 894 | local selfId = os.getComputerID() 895 | local runTerm = function() 896 | local fieldTb = { 897 | controlCenterId = newTextField(properties, "controlCenterId", 19, 14), 898 | velocity = newTextField(properties, "velocity", 11, 2), 899 | barrelLength = newTextField(properties, "barrelLength", 30, 2), 900 | gravity = newTextField(properties, "gravity", 45, 2), 901 | drag = newTextField(properties, "drag", 45, 3), 902 | min_pitch = newTextField(properties, "min_pitch", 12, 6), 903 | max_pitch = newTextField(properties, "max_pitch", 12, 7), 904 | min_yaw = newTextField(properties, "min_yaw", 27, 6), 905 | max_yaw = newTextField(properties, "max_yaw", 27, 7), 906 | forecastMov = newTextField(properties, "forecastMov", 46, 6), 907 | forecastRot = newTextField(properties, "forecastRot", 46, 7), 908 | cannonName = newTextField(properties, "cannonName", 14, 12), 909 | password = newTextField(properties, "password", 14, 13) 910 | } 911 | fieldTb.velocity.len = 5 912 | fieldTb.barrelLength.len = 3 913 | fieldTb.controlCenterId.len = 5 914 | fieldTb.password.len = 14 915 | fieldTb.min_pitch.len = 5 916 | fieldTb.max_pitch.len = 5 917 | fieldTb.min_yaw.len = 5 918 | fieldTb.max_yaw.len = 5 919 | fieldTb.gravity.len = 6 920 | fieldTb.drag.len = 6 921 | fieldTb.forecastMov.len = 5 922 | fieldTb.forecastRot.len = 5 923 | local selectBoxTb = { 924 | power_on = newSelectBox(properties, "power_on", 1, 12, 3, "top", "left", "right", "front", "back"), 925 | fire = newSelectBox(properties, "fire", 1, 12, 4, "top", "left", "right", "front", "back"), 926 | idleFace = newSelectBox(properties, "idleFace", 1, 14, 10, "south", "west", "north", "east"), 927 | inversion = newSelectBox(properties, "inversion", 1, 14, 8, false, true), 928 | basic_v = newSelectBox(properties, "basic_v", 1, 34, 8, false, true) 929 | } 930 | 931 | local alarm_id = os.setAlarm(os.time() + 0.05) 932 | local alarm_flag = false 933 | term.clear() 934 | term.setCursorPos(15, 8) 935 | term.write("Press any key to continue") 936 | while true do 937 | local eventData = {os.pullEvent()} 938 | local event = eventData[1] 939 | if event == "mouse_up" or event == "key_up" or event == "alarm" or event == "mouse_click" or event == 940 | "mouse_drag" or event == "key" or event == "char" then 941 | if not alarm_flag then 942 | alarm_flag = true 943 | else 944 | term.clear() 945 | term.setCursorPos(16, 1) 946 | printError(string.format("self id: %d", selfId)) 947 | if cannon then 948 | term.setCursorPos(36, 1) 949 | term.write(string.format("cannon on %s", cannon.face)) 950 | end 951 | 952 | term.setCursorPos(2, 2) 953 | term.write("Velocity") 954 | term.setCursorPos(17, 2) 955 | term.write("BarrelLength") 956 | term.setCursorPos(35, 2) 957 | term.write("gravity: ") 958 | term.setCursorPos(35, 3) 959 | term.write(" drag: ") 960 | 961 | term.setCursorPos(2, 3) 962 | term.write("POWER_ON: ") 963 | term.setCursorPos(2, 4) 964 | term.write("FIRE: ") 965 | 966 | term.setCursorPos(2, 6) 967 | term.write("Min_Pitch: ") 968 | term.setCursorPos(2, 7) 969 | term.write("Max_Pitch: ") 970 | term.setCursorPos(19, 6) 971 | term.write("Min_Yaw: ") 972 | term.setCursorPos(19, 7) 973 | term.write("Max_Yaw: ") 974 | 975 | term.setCursorPos(34, 6) 976 | term.write("forecastMov: ") 977 | term.setCursorPos(34, 7) 978 | term.write("forecastRot: ") 979 | 980 | term.setCursorPos(2, 8) 981 | term.write("inversion:") 982 | term.setCursorPos(26, 8) 983 | term.write("basic_v") 984 | term.setCursorPos(2, 10) 985 | term.write("idleFace: ") 986 | 987 | term.setCursorPos(2, 12) 988 | term.write("cannonName: ") 989 | term.setCursorPos(2, 13) 990 | term.write("Password: ") 991 | term.setCursorPos(2, 14) 992 | term.write("controlCenterId: ") 993 | 994 | for k, v in pairs(fieldTb) do 995 | v:paint() 996 | end 997 | 998 | for k, v in pairs(selectBoxTb) do 999 | v:paint() 1000 | end 1001 | 1002 | term.setCursorPos(termUtil.cpX, termUtil.cpY) 1003 | 1004 | if event == "mouse_click" then 1005 | term.setCursorBlink(true) 1006 | local x, y = eventData[3], eventData[4] 1007 | for k, v in pairs(fieldTb) do -- 点击了输入框 1008 | if y == v.y and x >= v.x and x <= v.x + v.len then 1009 | v:click(x, y) 1010 | end 1011 | end 1012 | for k, v in pairs(selectBoxTb) do -- 点击了选择框 1013 | if y == v.y then 1014 | v:click(x, y) 1015 | end 1016 | end 1017 | elseif event == "key" then 1018 | local key = eventData[2] 1019 | for k, v in pairs(fieldTb) do 1020 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1021 | v:inputKey(key) 1022 | end 1023 | end 1024 | elseif event == "char" then 1025 | local char = eventData[2] 1026 | for k, v in pairs(fieldTb) do 1027 | if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then 1028 | v:inputChar(char) 1029 | end 1030 | end 1031 | end 1032 | 1033 | -- 刷新数据到properties 1034 | system.updatePersistentData() 1035 | 1036 | if cannon.type == "createbigcannons:cannon_mount" then 1037 | if properties.inversion == true then 1038 | cannon.cross_offset.y = -2 1039 | else 1040 | cannon.cross_offset.y = 2 1041 | end 1042 | end 1043 | 1044 | initIdleAg() 1045 | setYawAndPitch(cannon.idle_yaw, 0) 1046 | end 1047 | end 1048 | end 1049 | end 1050 | 1051 | local checkFire = function() 1052 | while true do 1053 | if fire and ct > 0 then 1054 | if not redstone.getOutput(properties.fire) then 1055 | redstone.setOutput(properties.fire, true) 1056 | end 1057 | else 1058 | if redstone.getOutput(properties.fire) then 1059 | redstone.setOutput(properties.fire, false) 1060 | end 1061 | end 1062 | sleep(0.05) 1063 | end 1064 | end 1065 | 1066 | parallel.waitForAll(run_cannon, runTerm, getBullets_count, listener, sendRequest, checkFire) 1067 | --------------------------------------------------------------------------------